You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
source: clean up
This commit is contained in:
@@ -1,33 +0,0 @@
|
|||||||
name: Contribute to Unofficially Supported Devices
|
|
||||||
description: Add your device kernel source to KernelSU Next's Unofficially Supported Device List
|
|
||||||
title: "[Add Device]: "
|
|
||||||
labels: ["add-device"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thanks for supporting KernelSU Next!
|
|
||||||
- type: input
|
|
||||||
id: repo-url
|
|
||||||
attributes:
|
|
||||||
label: Repository URL
|
|
||||||
description: Your repository URL
|
|
||||||
placeholder: https://github.com/rifsxd/KernelSU-Next
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: device
|
|
||||||
attributes:
|
|
||||||
label: Device
|
|
||||||
description: Please describe the device maintained by you.
|
|
||||||
placeholder: GKI 2.0 Device
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
id: terms
|
|
||||||
attributes:
|
|
||||||
label: Code of Conduct
|
|
||||||
description: By submitting this issue, you should be the maintainer of the repository.
|
|
||||||
options:
|
|
||||||
- label: I'm the maintainer of this repository
|
|
||||||
required: true
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
name: Bug report
|
|
||||||
description: Create a report to help us improve KernelSU Next
|
|
||||||
labels: [Bug]
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Please check before submitting an issue
|
|
||||||
options:
|
|
||||||
- label: I have searched the issues and haven't found anything relevant
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- label: I will upload bugreport file in KernelSU Manager - Settings - Report log
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- label: I know how to reproduce the issue which may not be specific to my device
|
|
||||||
required: false
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Describe the bug
|
|
||||||
description: A clear and concise description of what the bug is
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: To Reproduce
|
|
||||||
description: Steps to reproduce the behaviour
|
|
||||||
placeholder: |
|
|
||||||
- 1. Go to '...'
|
|
||||||
- 2. Click on '....'
|
|
||||||
- 3. Scroll down to '....'
|
|
||||||
- 4. See error
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Expected behavior
|
|
||||||
description: A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Screenshots
|
|
||||||
description: If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Logs
|
|
||||||
description: If applicable, add crash or any other logs to help us figure out the problem.
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Device info
|
|
||||||
value: |
|
|
||||||
- Device:
|
|
||||||
- OS Version:
|
|
||||||
- Kernel Version:
|
|
||||||
- KSUN Driver Version:
|
|
||||||
- KSUN Manager Version:
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Additional context
|
|
||||||
description: Add any other context about the problem here.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
name: Custom issue template
|
|
||||||
description: WARNING! If you are reporting a bug but use this template, the issue will be closed directly.
|
|
||||||
title: '[Custom]'
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: description
|
|
||||||
attributes:
|
|
||||||
label: "Describe your problem."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
name: Feature Request
|
|
||||||
description: Suggest a new feature or improvement for KernelSU Next.
|
|
||||||
title: "[Feature] <short description>"
|
|
||||||
labels: ["enhancement"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thank you for taking the time to suggest a feature! Please fill out the following details.
|
|
||||||
- type: textarea
|
|
||||||
id: problem
|
|
||||||
attributes:
|
|
||||||
label: "Is your feature request related to a problem?"
|
|
||||||
description: "A clear and concise description of what the problem is. Ex: I'm always frustrated when..."
|
|
||||||
placeholder: "Describe the problem or need."
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: solution
|
|
||||||
attributes:
|
|
||||||
label: "Describe the solution you'd like"
|
|
||||||
description: "A clear and concise description of what you want to happen."
|
|
||||||
placeholder: "Describe your proposed solution."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: alternatives
|
|
||||||
attributes:
|
|
||||||
label: "Describe alternatives you've considered"
|
|
||||||
description: "A clear and concise description of any alternative solutions or features you've considered."
|
|
||||||
placeholder: "List any alternatives."
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: additional
|
|
||||||
attributes:
|
|
||||||
label: "Additional context"
|
|
||||||
description: "Add any other context or screenshots about the feature request here."
|
|
||||||
placeholder: "Additional context or screenshots."
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
groups:
|
|
||||||
actions:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
- package-ecosystem: cargo
|
|
||||||
directory: userspace/ksud_magic
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
allow:
|
|
||||||
- dependency-type: "all"
|
|
||||||
groups:
|
|
||||||
crates:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
- package-ecosystem: cargo
|
|
||||||
directory: userspace/ksud_overlayfs
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
allow:
|
|
||||||
- dependency-type: "all"
|
|
||||||
groups:
|
|
||||||
crates:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
- package-ecosystem: gradle
|
|
||||||
directory: manager
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
groups:
|
|
||||||
maven:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
|
||||||
<!--https://ci.android.com/builds/submitted/9964412/kernel_virt_x86_64/latest/manifest_9964412.xml-->
|
|
||||||
<manifest>
|
|
||||||
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/" />
|
|
||||||
|
|
||||||
<default revision="master" remote="aosp" sync-j="4" />
|
|
||||||
|
|
||||||
<superproject name="kernel/superproject" remote="aosp" revision="common-android14-6.1" />
|
|
||||||
|
|
||||||
<project path="build/kernel" name="kernel/build" revision="b0377a072bb3f78cdacfd6d809914a9d1b0c0148">
|
|
||||||
<linkfile dest="tools/bazel" src="kleaf/bazel.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE" />
|
|
||||||
|
|
||||||
<linkfile dest="build/build.sh" src="build.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="build/build_abi.sh" src="build_abi.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="build/build_test.sh" src="build_test.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="build/build_utils.sh" src="build_utils.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="build/config.sh" src="config.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="build/envsetup.sh" src="envsetup.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="build/_setup_env.sh" src="_setup_env.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="build/multi-switcher.sh" src="multi-switcher.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="build/abi" src="abi" />
|
|
||||||
|
|
||||||
<linkfile dest="build/static_analysis" src="static_analysis" />
|
|
||||||
</project>
|
|
||||||
|
|
||||||
<project path="common" name="kernel/common" revision="7e35917775b8b3e3346a87f294e334e258bf15e6">
|
|
||||||
<linkfile dest=".source_date_epoch_dir" src="." />
|
|
||||||
</project>
|
|
||||||
|
|
||||||
<project path="kernel/tests" name="kernel/tests" revision="c90a1c1b226b975cc31e709fa96fc1c6ecdbe272" />
|
|
||||||
|
|
||||||
<project path="kernel/configs" name="kernel/configs" revision="52a7267d6a9f9efabf3cb43839bb5e7f7ff05be3" />
|
|
||||||
|
|
||||||
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="0d03de3246301028775f05ea388c2c444344a268" />
|
|
||||||
|
|
||||||
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" clone-depth="1" revision="4f7e5adc160ab726ac5bafb260de98e612904c50" />
|
|
||||||
|
|
||||||
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" revision="f7b0d5b0ee369864d5ac3e96ae24ec9e2b6a52da" />
|
|
||||||
|
|
||||||
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" revision="dc92e06585a7647bf739a2309a721b82fcfa01d4" />
|
|
||||||
|
|
||||||
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="5611871963f54c688d3ac49e527aecdef21e8567" />
|
|
||||||
|
|
||||||
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" revision="2597cb1b5525e419b7fa806373be673054a68d29" />
|
|
||||||
|
|
||||||
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="2680066d0844544b3e78d6022cd21321d31837c3" />
|
|
||||||
|
|
||||||
<project path="prebuilts/bazel/linux-x86_64" name="platform/prebuilts/bazel/linux-x86_64" clone-depth="1" revision="4fdb9395071ff22118311d434d697c2b6fd887b4" />
|
|
||||||
|
|
||||||
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" clone-depth="1" revision="491e6aa056676f29c4541f71bd738e4e876e4ba2" />
|
|
||||||
|
|
||||||
<project path="prebuilts/ndk-r23" name="toolchain/prebuilts/ndk/r23" clone-depth="1" revision="19ac7e4eded12adb99d4f613490dde6dd0e72664" />
|
|
||||||
|
|
||||||
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" revision="f998e5dc13c03f0eae9e373263d3afff0932c738" />
|
|
||||||
|
|
||||||
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" revision="707b2c5fe3d0d7d934a93e00a8a4062e83557831" />
|
|
||||||
|
|
||||||
<project path="external/stardoc" name="platform/external/stardoc" revision="e83f522ee95419e55d2c5654aa6e0143beeef595" />
|
|
||||||
|
|
||||||
<project path="external/python/absl-py" name="platform/external/python/absl-py" revision="393d0b1e3f0fea3e95944a2fd3282cc9f76d4f14" />
|
|
||||||
</manifest>
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
|
||||||
<!-- https://ci.android.com/builds/submitted/11577653/kernel_virt_aarch64/latest/manifest_11577653.xml -->
|
|
||||||
<manifest>
|
|
||||||
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/" />
|
|
||||||
|
|
||||||
<default revision="main" remote="aosp" sync-j="4" />
|
|
||||||
|
|
||||||
<superproject name="kernel/superproject" remote="aosp" revision="common-android15-6.6" />
|
|
||||||
|
|
||||||
<project path="build/kernel" name="kernel/build" groups="ddk" revision="9a2196a1ec1048c2869750c9d3969c88ac18adcd">
|
|
||||||
<linkfile dest="tools/bazel" src="kleaf/bazel.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE" />
|
|
||||||
|
|
||||||
<linkfile dest="MODULE.bazel" src="kleaf/bzlmod/bazel.MODULE.bazel" />
|
|
||||||
|
|
||||||
<linkfile dest="WORKSPACE.bzlmod" src="kleaf/bzlmod/bazel.WORKSPACE.bzlmod" />
|
|
||||||
</project>
|
|
||||||
|
|
||||||
<project path="common" name="kernel/common" revision="ac1a7c65ff1bc7ece5569d62f02b121b4f2364f8" />
|
|
||||||
|
|
||||||
<project path="kernel/common-patches" name="kernel/common-patches" revision="3807ce65081de12ef4baa2a04487306672685160">
|
|
||||||
<linkfile dest="common/patches" src="android-mainline" />
|
|
||||||
</project>
|
|
||||||
|
|
||||||
<project path="kernel/tests" name="kernel/tests" revision="ca9fd66f5b48abc92990c9c770f73380b428362b" />
|
|
||||||
|
|
||||||
<project path="kernel/configs" name="kernel/configs" revision="be625f2ccf377a75d0ea86c082c716c322b8d4c6" />
|
|
||||||
|
|
||||||
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="60a24583ac921279e40a44f818040e40abb3ef46" />
|
|
||||||
|
|
||||||
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" revision="93a1369ba33743a87bdf0183373f590a36ff7cb1" clone-depth="1" groups="ddk" />
|
|
||||||
|
|
||||||
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" groups="ddk" revision="cef8f53bb61fbdb02dbf4d433004f6cb637c3bc6" />
|
|
||||||
|
|
||||||
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="5aca9957ab19d2668c7f1da1954bbe89652d5fed" />
|
|
||||||
|
|
||||||
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="69f9fb9b8e75c6f1ff01f380d5251757785bb823" />
|
|
||||||
|
|
||||||
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="b09295493adc8d804b6d24286660f6e451e387fd" />
|
|
||||||
|
|
||||||
<project path="prebuilts/rust" name="platform/prebuilts/rust" revision="adc0e5499c3ddac831ca596d12cbef8d9747f737" clone-depth="1" />
|
|
||||||
|
|
||||||
<project path="prebuilts/tradefed" name="platform/tools/tradefederation/prebuilts" clone-depth="1" revision="a76ca09c5593e22e65b0d823d508882c6c64c13e" />
|
|
||||||
|
|
||||||
<project path="prebuilts/asuite" name="platform/prebuilts/asuite" clone-depth="1" revision="24510f175cb313a92241500efee917c2930d5d30" />
|
|
||||||
|
|
||||||
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="28b7934249c2885db8b561f1439d74663fcdce93" />
|
|
||||||
|
|
||||||
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" revision="c6c90521b7c317f13d41bbd9336a8d45ee202cec" clone-depth="1" groups="ddk" />
|
|
||||||
|
|
||||||
<project path="prebuilts/ndk-r26" name="toolchain/prebuilts/ndk/r26" clone-depth="1" groups="ddk" revision="e535051ebc04204cec44bde38f62385d63180388" />
|
|
||||||
|
|
||||||
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" groups="ddk" revision="6b103c40d8113f001475d5e13672922ef2aa0e5a" />
|
|
||||||
|
|
||||||
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" groups="ddk" revision="2a10807a06153b5862da0369f4b6b368afc2dd08" />
|
|
||||||
|
|
||||||
<project path="external/libcap-ng" name="platform/external/libcap-ng" revision="2bcc92ae19481dd2b8d3ce3abdfbbee49261abe6" />
|
|
||||||
|
|
||||||
<project path="external/libcap" name="platform/external/libcap" revision="d7d1a0a38c5be06a7e7d6391d140b54878836f48" />
|
|
||||||
|
|
||||||
<project path="external/stardoc" name="platform/external/stardoc" groups="ddk" revision="f31250f9f5b03834d9964aaee7a3794c1d73d4a2" />
|
|
||||||
|
|
||||||
<project path="external/python/absl-py" name="platform/external/python/absl-py" groups="ddk" revision="9ae5a78fc57c3cd539398373ae39601a8b923e62" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-bazel-central-registry" name="platform/external/bazelbuild-bazel-central-registry" revision="3422f064566c274ea66633442521704d4a22486d" groups="ddk" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-platforms" name="platform/external/bazelbuild-platforms" groups="ddk" revision="e352aabd0131f3ac3f340282a43ba85ffc3fe8fa" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-apple_support" name="platform/external/bazelbuild-apple_support" groups="ddk" revision="f6003e1e3763f8aad9fb9acae79cfa5fff9ae988" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_cc" name="platform/external/bazelbuild-rules_cc" groups="ddk" revision="f0df148dbeb9b9ed3816aad328ebe7c65efaaa24" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_java" name="platform/external/bazelbuild-rules_java" groups="ddk" revision="8e548c7053dffd1717d565f0409a88992f401da1" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_license" name="platform/external/bazelbuild-rules_license" groups="ddk" revision="f578df4fd057ffe2023728444759535685631548" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_pkg" name="platform/external/bazelbuild-rules_pkg" groups="ddk" revision="429887dfd8db834498ad95e99043f771a3882af0" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_python" name="platform/external/bazelbuild-rules_python" groups="ddk" revision="f71847ac898655b67634bb14e77a7408c4fb5e00" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_rust" name="platform/external/bazelbuild-rules_rust" groups="ddk" revision="1520b49835be9122c2424231357d4db80069cc38" />
|
|
||||||
|
|
||||||
<project path="external/pigz" name="platform/external/pigz" groups="ddk" revision="9bc9fa17d499ddde88b77820f6d063e16c0cdd42" />
|
|
||||||
|
|
||||||
<project path="external/zlib" name="platform/external/zlib" groups="ddk" revision="eff168fd731068a3faddd9aae056875e10014a51" />
|
|
||||||
|
|
||||||
<project path="external/zopfli" name="platform/external/zopfli" groups="ddk" revision="36c79f00e5229800d2aaa13fc42c301ec8ef1153" />
|
|
||||||
</manifest>
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
|
||||||
<!-- https://ci.android.com/builds/submitted/11577653/kernel_virt_x86_64/latest/manifest_11577653.xml -->
|
|
||||||
<manifest>
|
|
||||||
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/" />
|
|
||||||
|
|
||||||
<default revision="main" remote="aosp" sync-j="4" />
|
|
||||||
|
|
||||||
<superproject name="kernel/superproject" remote="aosp" revision="common-android15-6.6" />
|
|
||||||
|
|
||||||
<project path="build/kernel" name="kernel/build" groups="ddk" revision="9a2196a1ec1048c2869750c9d3969c88ac18adcd">
|
|
||||||
<linkfile dest="tools/bazel" src="kleaf/bazel.sh" />
|
|
||||||
|
|
||||||
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE" />
|
|
||||||
|
|
||||||
<linkfile dest="MODULE.bazel" src="kleaf/bzlmod/bazel.MODULE.bazel" />
|
|
||||||
|
|
||||||
<linkfile dest="WORKSPACE.bzlmod" src="kleaf/bzlmod/bazel.WORKSPACE.bzlmod" />
|
|
||||||
</project>
|
|
||||||
|
|
||||||
<project path="common" name="kernel/common" revision="ac1a7c65ff1bc7ece5569d62f02b121b4f2364f8" />
|
|
||||||
|
|
||||||
<project path="kernel/common-patches" name="kernel/common-patches" revision="3807ce65081de12ef4baa2a04487306672685160">
|
|
||||||
<linkfile dest="common/patches" src="android-mainline" />
|
|
||||||
</project>
|
|
||||||
|
|
||||||
<project path="kernel/tests" name="kernel/tests" revision="ca9fd66f5b48abc92990c9c770f73380b428362b" />
|
|
||||||
|
|
||||||
<project path="kernel/configs" name="kernel/configs" revision="be625f2ccf377a75d0ea86c082c716c322b8d4c6" />
|
|
||||||
|
|
||||||
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="60a24583ac921279e40a44f818040e40abb3ef46" />
|
|
||||||
|
|
||||||
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" revision="93a1369ba33743a87bdf0183373f590a36ff7cb1" clone-depth="1" groups="ddk" />
|
|
||||||
|
|
||||||
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" groups="ddk" revision="cef8f53bb61fbdb02dbf4d433004f6cb637c3bc6" />
|
|
||||||
|
|
||||||
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="5aca9957ab19d2668c7f1da1954bbe89652d5fed" />
|
|
||||||
|
|
||||||
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="69f9fb9b8e75c6f1ff01f380d5251757785bb823" />
|
|
||||||
|
|
||||||
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="b09295493adc8d804b6d24286660f6e451e387fd" />
|
|
||||||
|
|
||||||
<project path="prebuilts/rust" name="platform/prebuilts/rust" revision="adc0e5499c3ddac831ca596d12cbef8d9747f737" clone-depth="1" />
|
|
||||||
|
|
||||||
<project path="prebuilts/tradefed" name="platform/tools/tradefederation/prebuilts" clone-depth="1" revision="a76ca09c5593e22e65b0d823d508882c6c64c13e" />
|
|
||||||
|
|
||||||
<project path="prebuilts/asuite" name="platform/prebuilts/asuite" clone-depth="1" revision="24510f175cb313a92241500efee917c2930d5d30" />
|
|
||||||
|
|
||||||
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="28b7934249c2885db8b561f1439d74663fcdce93" />
|
|
||||||
|
|
||||||
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" revision="c6c90521b7c317f13d41bbd9336a8d45ee202cec" clone-depth="1" groups="ddk" />
|
|
||||||
|
|
||||||
<project path="prebuilts/ndk-r26" name="toolchain/prebuilts/ndk/r26" clone-depth="1" groups="ddk" revision="e535051ebc04204cec44bde38f62385d63180388" />
|
|
||||||
|
|
||||||
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" groups="ddk" revision="6b103c40d8113f001475d5e13672922ef2aa0e5a" />
|
|
||||||
|
|
||||||
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" groups="ddk" revision="2a10807a06153b5862da0369f4b6b368afc2dd08" />
|
|
||||||
|
|
||||||
<project path="external/libcap-ng" name="platform/external/libcap-ng" revision="2bcc92ae19481dd2b8d3ce3abdfbbee49261abe6" />
|
|
||||||
|
|
||||||
<project path="external/libcap" name="platform/external/libcap" revision="d7d1a0a38c5be06a7e7d6391d140b54878836f48" />
|
|
||||||
|
|
||||||
<project path="external/stardoc" name="platform/external/stardoc" groups="ddk" revision="f31250f9f5b03834d9964aaee7a3794c1d73d4a2" />
|
|
||||||
|
|
||||||
<project path="external/python/absl-py" name="platform/external/python/absl-py" groups="ddk" revision="9ae5a78fc57c3cd539398373ae39601a8b923e62" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-bazel-central-registry" name="platform/external/bazelbuild-bazel-central-registry" revision="3422f064566c274ea66633442521704d4a22486d" groups="ddk" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-platforms" name="platform/external/bazelbuild-platforms" groups="ddk" revision="e352aabd0131f3ac3f340282a43ba85ffc3fe8fa" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-apple_support" name="platform/external/bazelbuild-apple_support" groups="ddk" revision="f6003e1e3763f8aad9fb9acae79cfa5fff9ae988" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_cc" name="platform/external/bazelbuild-rules_cc" groups="ddk" revision="f0df148dbeb9b9ed3816aad328ebe7c65efaaa24" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_java" name="platform/external/bazelbuild-rules_java" groups="ddk" revision="8e548c7053dffd1717d565f0409a88992f401da1" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_license" name="platform/external/bazelbuild-rules_license" groups="ddk" revision="f578df4fd057ffe2023728444759535685631548" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_pkg" name="platform/external/bazelbuild-rules_pkg" groups="ddk" revision="429887dfd8db834498ad95e99043f771a3882af0" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_python" name="platform/external/bazelbuild-rules_python" groups="ddk" revision="f71847ac898655b67634bb14e77a7408c4fb5e00" />
|
|
||||||
|
|
||||||
<project path="external/bazelbuild-rules_rust" name="platform/external/bazelbuild-rules_rust" groups="ddk" revision="1520b49835be9122c2424231357d4db80069cc38" />
|
|
||||||
|
|
||||||
<project path="external/pigz" name="platform/external/pigz" groups="ddk" revision="9bc9fa17d499ddde88b77820f6d063e16c0cdd42" />
|
|
||||||
|
|
||||||
<project path="external/zlib" name="platform/external/zlib" groups="ddk" revision="eff168fd731068a3faddd9aae056875e10014a51" />
|
|
||||||
|
|
||||||
<project path="external/zopfli" name="platform/external/zopfli" groups="ddk" revision="36c79f00e5229800d2aaa13fc42c301ec8ef1153" />
|
|
||||||
</manifest>
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
build_from_image() {
|
|
||||||
export TITLE
|
|
||||||
TITLE=kernel-aarch64-${1//Image-/}
|
|
||||||
echo "[+] title: $TITLE"
|
|
||||||
|
|
||||||
export PATCH_LEVEL
|
|
||||||
PATCH_LEVEL=$(echo "$1" | awk -F_ '{ print $2}')
|
|
||||||
echo "[+] patch level: $PATCH_LEVEL"
|
|
||||||
|
|
||||||
echo '[+] Download prebuilt ramdisk'
|
|
||||||
GKI_URL=https://dl.google.com/android/gki/gki-certified-boot-android12-5.10-"${PATCH_LEVEL}"_r1.zip
|
|
||||||
FALLBACK_URL=https://dl.google.com/android/gki/gki-certified-boot-android12-5.10-2023-01_r1.zip
|
|
||||||
status=$(curl -sL -w "%{http_code}" "$GKI_URL" -o /dev/null)
|
|
||||||
if [ "$status" = "200" ]; then
|
|
||||||
curl -Lo gki-kernel.zip "$GKI_URL"
|
|
||||||
else
|
|
||||||
echo "[+] $GKI_URL not found, using $FALLBACK_URL"
|
|
||||||
curl -Lo gki-kernel.zip "$FALLBACK_URL"
|
|
||||||
fi
|
|
||||||
unzip gki-kernel.zip && rm gki-kernel.zip
|
|
||||||
|
|
||||||
echo '[+] Unpack prebuilt boot.img'
|
|
||||||
BOOT_IMG=$(find . -maxdepth 1 -name "boot*.img")
|
|
||||||
$UNPACK_BOOTIMG --boot_img="$BOOT_IMG"
|
|
||||||
rm "$BOOT_IMG"
|
|
||||||
|
|
||||||
echo '[+] Building Image.gz'
|
|
||||||
$GZIP -n -k -f -9 Image >Image.gz
|
|
||||||
|
|
||||||
echo '[+] Building boot.img'
|
|
||||||
$MKBOOTIMG --header_version 4 --kernel Image --output boot.img --ramdisk out/ramdisk --os_version 12.0.0 --os_patch_level "${PATCH_LEVEL}"
|
|
||||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
|
|
||||||
echo '[+] Building boot-gz.img'
|
|
||||||
$MKBOOTIMG --header_version 4 --kernel Image.gz --output boot-gz.img --ramdisk out/ramdisk --os_version 12.0.0 --os_patch_level "${PATCH_LEVEL}"
|
|
||||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot-gz.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
|
|
||||||
echo '[+] Building boot-lz4.img'
|
|
||||||
$MKBOOTIMG --header_version 4 --kernel Image.lz4 --output boot-lz4.img --ramdisk out/ramdisk --os_version 12.0.0 --os_patch_level "${PATCH_LEVEL}"
|
|
||||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot-lz4.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
|
|
||||||
echo '[+] Compress images'
|
|
||||||
for image in boot*.img; do
|
|
||||||
$GZIP -n -f -9 "$image"
|
|
||||||
mv "$image".gz "${1//Image-/}"-"$image".gz
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[+] Images to upload"
|
|
||||||
find . -type f -name "*.gz"
|
|
||||||
|
|
||||||
# find . -type f -name "*.gz" -exec python3 "$GITHUB_WORKSPACE"/KernelSU-Next/scripts/ksunextbot.py {} +
|
|
||||||
}
|
|
||||||
|
|
||||||
for dir in Image*; do
|
|
||||||
if [ -d "$dir" ]; then
|
|
||||||
echo "----- Building $dir -----"
|
|
||||||
cd "$dir"
|
|
||||||
build_from_image "$dir"
|
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
build_from_image() {
|
|
||||||
export TITLE
|
|
||||||
TITLE=kernel-aarch64-${1//Image-/}
|
|
||||||
|
|
||||||
echo "[+] title: $TITLE"
|
|
||||||
echo '[+] Building Image.gz'
|
|
||||||
$GZIP -n -k -f -9 Image >Image.gz
|
|
||||||
|
|
||||||
echo '[+] Building boot.img'
|
|
||||||
$MKBOOTIMG --header_version 4 --kernel Image --output boot.img
|
|
||||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
|
|
||||||
echo '[+] Building boot-gz.img'
|
|
||||||
$MKBOOTIMG --header_version 4 --kernel Image.gz --output boot-gz.img
|
|
||||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot-gz.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
|
|
||||||
echo '[+] Building boot-lz4.img'
|
|
||||||
$MKBOOTIMG --header_version 4 --kernel Image.lz4 --output boot-lz4.img
|
|
||||||
$AVBTOOL add_hash_footer --partition_name boot --partition_size $((64 * 1024 * 1024)) --image boot-lz4.img --algorithm SHA256_RSA2048 --key ../kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
|
|
||||||
echo '[+] Compress images'
|
|
||||||
for image in boot*.img; do
|
|
||||||
$GZIP -n -f -9 "$image"
|
|
||||||
mv "$image".gz "${1//Image-/}"-"$image".gz
|
|
||||||
done
|
|
||||||
|
|
||||||
echo '[+] Images to upload'
|
|
||||||
find . -type f -name "*.gz"
|
|
||||||
|
|
||||||
# find . -type f -name "*.gz" -exec python3 "$GITHUB_WORKSPACE"/KernelSU-Next/scripts/ksunextbot.py {} +
|
|
||||||
}
|
|
||||||
|
|
||||||
for dir in Image*; do
|
|
||||||
if [ -d "$dir" ]; then
|
|
||||||
echo "----- Building $dir -----"
|
|
||||||
cd "$dir"
|
|
||||||
build_from_image "$dir"
|
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
@@ -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-Next
|
|
||||||
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-Next/.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-Next
|
|
||||||
env:
|
|
||||||
PATCH_PATH: ${{ inputs.patch_path }}
|
|
||||||
IS_DEBUG_KERNEL: ${{ inputs.debug }}
|
|
||||||
run: |
|
|
||||||
cd $GITHUB_WORKSPACE/android-kernel
|
|
||||||
echo "[+] KernelSU-Next setup"
|
|
||||||
GKI_ROOT=$(pwd)
|
|
||||||
echo "[+] GKI_ROOT: $GKI_ROOT"
|
|
||||||
echo "[+] Copy KernelSU-Next driver to $GKI_ROOT/common/drivers"
|
|
||||||
ln -sf $GITHUB_WORKSPACE/KernelSU-Next/kernel $GKI_ROOT/common/drivers/kernelsu
|
|
||||||
echo "[+] Add KernelSU-Next 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-Next patches"
|
|
||||||
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU-Next/.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-Next/kernel/Makefile
|
|
||||||
fi
|
|
||||||
repo status
|
|
||||||
echo "[+] KernelSU-Next setup done."
|
|
||||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
|
||||||
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 "KernelSU-NextBot"
|
|
||||||
cd common/ && git add -A && git commit -a -m "Add KernelSU-Next"
|
|
||||||
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 }}"
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
name: Build debug kernel
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-debug-kernel-a12:
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
with:
|
|
||||||
version: android12-5.10
|
|
||||||
version_name: android12-5.10.236
|
|
||||||
tag: android12-5.10-2025-05
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
patch_path: "5.10"
|
|
||||||
debug: true
|
|
||||||
build-debug-kernel-a13:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 236
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 180
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
with:
|
|
||||||
version: android13-${{ matrix.version }}
|
|
||||||
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
debug: true
|
|
||||||
build-debug-kernel-a14:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 180
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 138
|
|
||||||
os_patch_level: 2025-06
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
with:
|
|
||||||
version: android14-${{ matrix.version }}
|
|
||||||
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
debug: true
|
|
||||||
build-debug-kernel-a15:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 89
|
|
||||||
os_patch_level: 2025-06
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
with:
|
|
||||||
version: android15-${{ matrix.version }}
|
|
||||||
version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
debug: true
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
name: Build Kernel - Android 12
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-a12.yml"
|
|
||||||
- ".github/workflows/gki-kernel.yml"
|
|
||||||
- ".github/scripts/build_a12.sh"
|
|
||||||
- "kernel/**"
|
|
||||||
pull_request:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-a12.yml"
|
|
||||||
- ".github/workflows/gki-kernel.yml"
|
|
||||||
- ".github/scripts/build-a12.sh"
|
|
||||||
- "kernel/**"
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
build-kernel:
|
|
||||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/next'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- sub_level: 218
|
|
||||||
os_patch_level: 2024-08
|
|
||||||
- sub_level: 226
|
|
||||||
os_patch_level: 2024-11
|
|
||||||
- sub_level: 233
|
|
||||||
os_patch_level: 2025-02
|
|
||||||
- sub_level: 236
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
version: android12-5.10
|
|
||||||
version_name: android12-5.10.${{ matrix.sub_level }}
|
|
||||||
tag: android12-5.10-${{ matrix.os_patch_level }}
|
|
||||||
os_patch_level: ${{ matrix.os_patch_level }}
|
|
||||||
patch_path: "5.10"
|
|
||||||
|
|
||||||
upload-artifacts:
|
|
||||||
needs: build-kernel
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/next' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/next' }}
|
|
||||||
env:
|
|
||||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
|
||||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
|
||||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
||||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
steps:
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
path: KernelSU-Next
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: List artifacts
|
|
||||||
run: |
|
|
||||||
tree
|
|
||||||
|
|
||||||
- name: Download prebuilt toolchain
|
|
||||||
run: |
|
|
||||||
AOSP_MIRROR=https://android.googlesource.com
|
|
||||||
BRANCH=main-kernel-build-2024
|
|
||||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
|
||||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
|
||||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
|
||||||
pip3 install telethon
|
|
||||||
|
|
||||||
- name: Set boot sign key
|
|
||||||
env:
|
|
||||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
|
||||||
run: |
|
|
||||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
|
||||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Bot session cache
|
|
||||||
id: bot_session_cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
if: false
|
|
||||||
with:
|
|
||||||
path: scripts/ksunextbot.session
|
|
||||||
key: ${{ runner.os }}-bot-session
|
|
||||||
|
|
||||||
- name: Build boot images
|
|
||||||
run: |
|
|
||||||
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
|
|
||||||
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
|
|
||||||
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
|
|
||||||
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
|
|
||||||
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
|
|
||||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
|
||||||
export VERSION=$(($(git rev-list --count HEAD) + 10200))
|
|
||||||
echo "VERSION: $VERSION"
|
|
||||||
cd -
|
|
||||||
bash $GITHUB_WORKSPACE/KernelSU-Next/.github/scripts/build_a12.sh
|
|
||||||
|
|
||||||
- name: Display structure of boot files
|
|
||||||
run: ls -R
|
|
||||||
|
|
||||||
- name: Upload images artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: boot-images-android12
|
|
||||||
path: Image-android12*/*.img.gz
|
|
||||||
|
|
||||||
check-build-kernel:
|
|
||||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/next'
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
with:
|
|
||||||
version: android12-5.10
|
|
||||||
version_name: android12-5.10.236
|
|
||||||
tag: android12-5.10-2025-05
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
patch_path: "5.10"
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
name: Build Kernel - Android 13
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-a13.yml"
|
|
||||||
- ".github/workflows/gki-kernel.yml"
|
|
||||||
- ".github/scripts/build_a13.sh"
|
|
||||||
- "kernel/**"
|
|
||||||
pull_request:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-a13.yml"
|
|
||||||
- ".github/workflows/gki-kernel.yml"
|
|
||||||
- ".github/scripts/build-a13.sh"
|
|
||||||
- "kernel/**"
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
build-kernel:
|
|
||||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/next'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 210
|
|
||||||
os_patch_level: 2024-06
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 214
|
|
||||||
os_patch_level: 2024-07
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 218
|
|
||||||
os_patch_level: 2024-08
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 223
|
|
||||||
os_patch_level: 2024-11
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 228
|
|
||||||
os_patch_level: 2025-01
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 234
|
|
||||||
os_patch_level: 2025-03
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 236
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 149
|
|
||||||
os_patch_level: 2024-07
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 151
|
|
||||||
os_patch_level: 2024-08
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 153
|
|
||||||
os_patch_level: 2024-09
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 167
|
|
||||||
os_patch_level: 2024-11
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 170
|
|
||||||
os_patch_level: 2025-01
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 178
|
|
||||||
os_patch_level: 2025-03
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 180
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
version: android13-${{ matrix.version }}
|
|
||||||
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
os_patch_level: ${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
|
|
||||||
upload-artifacts:
|
|
||||||
needs: build-kernel
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/next' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/next' }}
|
|
||||||
env:
|
|
||||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
|
||||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
|
||||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
||||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
steps:
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
path: KernelSU-Next
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: List artifacts
|
|
||||||
run: |
|
|
||||||
tree
|
|
||||||
|
|
||||||
- name: Download prebuilt toolchain
|
|
||||||
run: |
|
|
||||||
AOSP_MIRROR=https://android.googlesource.com
|
|
||||||
BRANCH=main-kernel-build-2024
|
|
||||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
|
||||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
|
||||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
|
||||||
pip3 install telethon
|
|
||||||
|
|
||||||
- name: Set boot sign key
|
|
||||||
env:
|
|
||||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
|
||||||
run: |
|
|
||||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
|
||||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Bot session cache
|
|
||||||
id: bot_session_cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
if: false
|
|
||||||
with:
|
|
||||||
path: scripts/ksunextbot.session
|
|
||||||
key: ${{ runner.os }}-bot-session
|
|
||||||
|
|
||||||
- name: Build boot images
|
|
||||||
run: |
|
|
||||||
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
|
|
||||||
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
|
|
||||||
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
|
|
||||||
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
|
|
||||||
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
|
|
||||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
|
||||||
export VERSION=$(($(git rev-list --count HEAD) + 10200))
|
|
||||||
echo "VERSION: $VERSION"
|
|
||||||
cd -
|
|
||||||
bash $GITHUB_WORKSPACE/KernelSU-Next/.github/scripts/build_a13.sh
|
|
||||||
|
|
||||||
- name: Display structure of boot files
|
|
||||||
run: ls -R
|
|
||||||
|
|
||||||
- name: Upload images artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: boot-images-android13
|
|
||||||
path: Image-android13*/*.img.gz
|
|
||||||
|
|
||||||
check-build-kernel:
|
|
||||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/next'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "5.10"
|
|
||||||
sub_level: 236
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 180
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
with:
|
|
||||||
version: android13-${{ matrix.version }}
|
|
||||||
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
os_patch_level: ${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
name: Build Kernel - Android 14
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-a14.yml"
|
|
||||||
- ".github/workflows/gki-kernel.yml"
|
|
||||||
- ".github/scripts/build_a13.sh"
|
|
||||||
- "kernel/**"
|
|
||||||
pull_request:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-a14.yml"
|
|
||||||
- ".github/workflows/gki-kernel.yml"
|
|
||||||
- ".github/scripts/build-a13.sh"
|
|
||||||
- "kernel/**"
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
build-kernel:
|
|
||||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/next'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 149
|
|
||||||
os_patch_level: 2024-06
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 153
|
|
||||||
os_patch_level: 2024-07
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 158
|
|
||||||
os_patch_level: 2024-08
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 164
|
|
||||||
os_patch_level: 2024-09
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 167
|
|
||||||
os_patch_level: 2024-11
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 170
|
|
||||||
os_patch_level: 2025-01
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 178
|
|
||||||
os_patch_level: 2025-03
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 180
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 78
|
|
||||||
os_patch_level: 2024-06
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 84
|
|
||||||
os_patch_level: 2024-07
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 90
|
|
||||||
os_patch_level: 2024-08
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 93
|
|
||||||
os_patch_level: 2024-09
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 99
|
|
||||||
os_patch_level: 2024-10
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 112
|
|
||||||
os_patch_level: 2024-11
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 115
|
|
||||||
os_patch_level: 2024-12
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 118
|
|
||||||
os_patch_level: 2025-01
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 124
|
|
||||||
os_patch_level: 2025-02
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 128
|
|
||||||
os_patch_level: 2025-03
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 129
|
|
||||||
os_patch_level: 2025-04
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 134
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 138
|
|
||||||
os_patch_level: 2025-06
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
version: android14-${{ matrix.version }}
|
|
||||||
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
os_patch_level: ${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
|
|
||||||
upload-artifacts:
|
|
||||||
needs: build-kernel
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/next' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/next' }}
|
|
||||||
env:
|
|
||||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
|
||||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
|
||||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
||||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
steps:
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
path: KernelSU-Next
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: List artifacts
|
|
||||||
run: |
|
|
||||||
tree
|
|
||||||
|
|
||||||
- name: Download prebuilt toolchain
|
|
||||||
run: |
|
|
||||||
AOSP_MIRROR=https://android.googlesource.com
|
|
||||||
BRANCH=main-kernel-build-2024
|
|
||||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
|
||||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
|
||||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
|
||||||
pip3 install telethon
|
|
||||||
|
|
||||||
- name: Set boot sign key
|
|
||||||
env:
|
|
||||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
|
||||||
run: |
|
|
||||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
|
||||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Bot session cache
|
|
||||||
id: bot_session_cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
if: false
|
|
||||||
with:
|
|
||||||
path: scripts/ksunextbot.session
|
|
||||||
key: ${{ runner.os }}-bot-session
|
|
||||||
|
|
||||||
- name: Build boot images
|
|
||||||
run: |
|
|
||||||
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
|
|
||||||
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
|
|
||||||
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
|
|
||||||
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
|
|
||||||
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
|
|
||||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
|
||||||
export VERSION=$(($(git rev-list --count HEAD) + 10200))
|
|
||||||
echo "VERSION: $VERSION"
|
|
||||||
cd -
|
|
||||||
bash $GITHUB_WORKSPACE/KernelSU-Next/.github/scripts/build_a13.sh
|
|
||||||
|
|
||||||
- name: Display structure of boot files
|
|
||||||
run: ls -R
|
|
||||||
|
|
||||||
- name: Upload images artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: boot-images-android14
|
|
||||||
path: Image-android14*/*.img.gz
|
|
||||||
|
|
||||||
check-build-kernel:
|
|
||||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/next'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "5.15"
|
|
||||||
sub_level: 180
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "6.1"
|
|
||||||
sub_level: 138
|
|
||||||
os_patch_level: 2025-06
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
with:
|
|
||||||
version: android14-${{ matrix.version }}
|
|
||||||
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
os_patch_level: ${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
name: Build Kernel - Android 15
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-a15.yml"
|
|
||||||
- ".github/workflows/gki-kernel.yml"
|
|
||||||
- ".github/scripts/build_a13.sh"
|
|
||||||
- "kernel/**"
|
|
||||||
pull_request:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-a15.yml"
|
|
||||||
- ".github/workflows/gki-kernel.yml"
|
|
||||||
- ".github/scripts/build-a13.sh"
|
|
||||||
- "kernel/**"
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
build-kernel:
|
|
||||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/next'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 30
|
|
||||||
os_patch_level: 2024-08
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 46
|
|
||||||
os_patch_level: 2024-09
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 50
|
|
||||||
os_patch_level: 2024-10
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 56
|
|
||||||
os_patch_level: 2024-11
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 57
|
|
||||||
os_patch_level: 2024-12
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 58
|
|
||||||
os_patch_level: 2025-01
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 66
|
|
||||||
os_patch_level: 2025-02
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 77
|
|
||||||
os_patch_level: 2025-03
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 82
|
|
||||||
os_patch_level: 2025-04
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 87
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 89
|
|
||||||
os_patch_level: 2025-06
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
version: android15-${{ matrix.version }}
|
|
||||||
version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
os_patch_level: ${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
|
|
||||||
upload-artifacts:
|
|
||||||
needs: build-kernel
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/next' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/next' }}
|
|
||||||
env:
|
|
||||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
|
||||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
|
||||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
||||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
steps:
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
path: KernelSU-Next
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: List artifacts
|
|
||||||
run: |
|
|
||||||
tree
|
|
||||||
|
|
||||||
- name: Download prebuilt toolchain
|
|
||||||
run: |
|
|
||||||
AOSP_MIRROR=https://android.googlesource.com
|
|
||||||
BRANCH=main-kernel-build-2024
|
|
||||||
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
|
|
||||||
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
|
|
||||||
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
|
|
||||||
pip3 install telethon
|
|
||||||
|
|
||||||
- name: Set boot sign key
|
|
||||||
env:
|
|
||||||
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
|
|
||||||
run: |
|
|
||||||
if [ ! -z "$BOOT_SIGN_KEY" ]; then
|
|
||||||
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Bot session cache
|
|
||||||
id: bot_session_cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
if: false
|
|
||||||
with:
|
|
||||||
path: scripts/ksunextbot.session
|
|
||||||
key: ${{ runner.os }}-bot-session
|
|
||||||
|
|
||||||
- name: Build boot images
|
|
||||||
run: |
|
|
||||||
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
|
|
||||||
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
|
|
||||||
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
|
|
||||||
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
|
|
||||||
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
|
|
||||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
|
||||||
export VERSION=$(($(git rev-list --count HEAD) + 10200))
|
|
||||||
echo "VERSION: $VERSION"
|
|
||||||
cd -
|
|
||||||
bash $GITHUB_WORKSPACE/KernelSU-Next/.github/scripts/build_a13.sh
|
|
||||||
|
|
||||||
- name: Display structure of boot files
|
|
||||||
run: ls -R
|
|
||||||
|
|
||||||
- name: Upload images artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: boot-images-android15
|
|
||||||
path: Image-android15*/*.img.gz
|
|
||||||
|
|
||||||
check-build-kernel:
|
|
||||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/next'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "6.6"
|
|
||||||
sub_level: 89
|
|
||||||
os_patch_level: 2025-06
|
|
||||||
uses: ./.github/workflows/gki-kernel.yml
|
|
||||||
with:
|
|
||||||
version: android15-${{ matrix.version }}
|
|
||||||
version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }}
|
|
||||||
tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }}
|
|
||||||
os_patch_level: ${{ matrix.os_patch_level }}
|
|
||||||
patch_path: ${{ matrix.version }}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
name: Build Kernel - ChromeOS ARCVM
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["next"]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-arcvm.yml"
|
|
||||||
- "kernel/**"
|
|
||||||
pull_request:
|
|
||||||
branches: ["next"]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-arcvm.yml"
|
|
||||||
- "kernel/**"
|
|
||||||
workflow_call:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
git_tag: chromeos-5.10-arcvm
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- arch: x86_64
|
|
||||||
kernel_image_name: bzImage
|
|
||||||
build_config: build.config.gki.x86_64
|
|
||||||
defconfig: x86_64_arcvm_defconfig
|
|
||||||
- arch: arm64
|
|
||||||
kernel_image_name: Image
|
|
||||||
build_config: build.config.gki.aarch64
|
|
||||||
defconfig: arm64_arcvm_defconfig
|
|
||||||
|
|
||||||
name: Build ChromeOS ARCVM kernel
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
env:
|
|
||||||
LTO: thin
|
|
||||||
ROOT_DIR: /
|
|
||||||
KERNEL_DIR: ${{ github.workspace }}/kernel
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Install Build Tools
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y --no-install-recommends bc \
|
|
||||||
bison build-essential ca-certificates flex git gnupg \
|
|
||||||
libelf-dev libssl-dev lsb-release software-properties-common wget \
|
|
||||||
libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget gzip \
|
|
||||||
rsync python3 device-tree-compiler
|
|
||||||
|
|
||||||
sudo ln -s --force python3 /usr/bin/python
|
|
||||||
|
|
||||||
export LLVM_VERSION=14
|
|
||||||
wget https://apt.llvm.org/llvm.sh
|
|
||||||
chmod +x llvm.sh
|
|
||||||
sudo ./llvm.sh $LLVM_VERSION
|
|
||||||
rm ./llvm.sh
|
|
||||||
sudo ln -s --force /usr/bin/clang-$LLVM_VERSION /usr/bin/clang
|
|
||||||
sudo ln -s --force /usr/bin/ld.lld-$LLVM_VERSION /usr/bin/ld.lld
|
|
||||||
sudo ln -s --force /usr/bin/llvm-objdump-$LLVM_VERSION /usr/bin/llvm-objdump
|
|
||||||
sudo ln -s --force /usr/bin/llvm-ar-$LLVM_VERSION /usr/bin/llvm-ar
|
|
||||||
sudo ln -s --force /usr/bin/llvm-nm-$LLVM_VERSION /usr/bin/llvm-nm
|
|
||||||
sudo ln -s --force /usr/bin/llvm-strip-$LLVM_VERSION /usr/bin/llvm-strip
|
|
||||||
sudo ln -s --force /usr/bin/llvm-objcopy-$LLVM_VERSION /usr/bin/llvm-objcopy
|
|
||||||
sudo ln -s --force /usr/bin/llvm-readelf-$LLVM_VERSION /usr/bin/llvm-readelf
|
|
||||||
sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++
|
|
||||||
|
|
||||||
- name: Checkout KernelSU-Next
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
path: KernelSU-Next
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup kernel source
|
|
||||||
run: git clone https://chromium.googlesource.com/chromiumos/third_party/kernel.git -b ${{ env.git_tag }} --depth=1
|
|
||||||
|
|
||||||
- name: Extract version from Makefile
|
|
||||||
working-directory: kernel
|
|
||||||
run: |
|
|
||||||
VERSION=$(grep -E '^VERSION = ' Makefile | awk '{print $3}')
|
|
||||||
PATCHLEVEL=$(grep -E '^PATCHLEVEL = ' Makefile | awk '{print $3}')
|
|
||||||
SUBLEVEL=$(grep -E '^SUBLEVEL = ' Makefile | awk '{print $3}')
|
|
||||||
echo "ChromeOS ARCVM Linux kernel version: $VERSION.$PATCHLEVEL.$SUBLEVEL"
|
|
||||||
echo "version=$VERSION.$PATCHLEVEL.$SUBLEVEL" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Setup KernelSU-Next
|
|
||||||
working-directory: kernel
|
|
||||||
run: |
|
|
||||||
echo "[+] KernelSU-Next setup"
|
|
||||||
KERNEL_ROOT=$GITHUB_WORKSPACE/kernel
|
|
||||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
|
||||||
echo "[+] Copy KernelSU-Next driver to $KERNEL_ROOT/drivers"
|
|
||||||
ln -sf $GITHUB_WORKSPACE/KernelSU-Next/kernel $KERNEL_ROOT/drivers/kernelsu-next
|
|
||||||
|
|
||||||
echo "[+] Add KernelSU-Next driver to Makefile"
|
|
||||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
|
||||||
DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig
|
|
||||||
grep -q "kernelsu-next" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu-next/\n" >> "$DRIVER_MAKEFILE"
|
|
||||||
grep -q "kernelsu-next" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu-next/Kconfig\"" "$DRIVER_KCONFIG"
|
|
||||||
|
|
||||||
echo "[+] Apply KernelSU patches"
|
|
||||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU-Next/.github/patches/5.10/*.patch || echo "[-] No patch found"
|
|
||||||
|
|
||||||
echo "[+] Patch script/setlocalversion"
|
|
||||||
sed -i 's/-dirty//g' $KERNEL_ROOT/scripts/setlocalversion
|
|
||||||
|
|
||||||
echo "[+] KernelSU-Next setup done."
|
|
||||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
|
||||||
KSU_VERSION=$(($(git rev-list --count HEAD) + 10200))
|
|
||||||
echo "KernelSU-Next version: $KSU_VERSION"
|
|
||||||
echo "kernelsu-next_version=$KSU_VERSION" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build Kernel
|
|
||||||
working-directory: kernel
|
|
||||||
env:
|
|
||||||
KERNEL_IMAGE_NAME: ${{ matrix.kernel_image_name }}
|
|
||||||
ARCH: ${{ matrix.arch }}
|
|
||||||
run: |
|
|
||||||
set -a && . ${{ matrix.build_config }}; set +a
|
|
||||||
export DEFCONFIG=${{ matrix.defconfig }}
|
|
||||||
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
|
|
||||||
|
|
||||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} mrproper
|
|
||||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} ${DEFCONFIG} < /dev/null
|
|
||||||
scripts/config --file .config -e LTO_CLANG -d LTO_NONE -e LTO_CLANG_THIN -d LTO_CLANG_FULL -e THINLTO
|
|
||||||
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} -j$(nproc) ${KERNEL_IMAGE_NAME} modules prepare-objtool
|
|
||||||
ls -l -h ${PWD}/arch/${ARCH}/boot
|
|
||||||
echo "file_path=${PWD}/arch/${ARCH}/boot/${KERNEL_IMAGE_NAME}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Upload kernel-ARCVM-${{ matrix.arch }}-${{ env.version }}
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: kernel-ARCVM-${{ matrix.arch }}-${{ env.version }}
|
|
||||||
path: "${{ env.file_path }}"
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
name: Build Kernel - AVD
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["next"]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-avd.yml"
|
|
||||||
- ".github/workflows/avd-kernel.yml"
|
|
||||||
- "kernel/**"
|
|
||||||
pull_request:
|
|
||||||
branches: ["next"]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-avd.yml"
|
|
||||||
- ".github/workflows/avd-kernel.yml"
|
|
||||||
- "kernel/**"
|
|
||||||
workflow_call:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-kernel:
|
|
||||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
|
||||||
uses: ./.github/workflows/avd-kernel.yml
|
|
||||||
secrets: inherit
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "android-14-avd_x86_64"
|
|
||||||
manifest: "android-14-avd_x86_64.xml"
|
|
||||||
arch: "x86_64"
|
|
||||||
- version: "android-15-avd_aarch64"
|
|
||||||
manifest: "android-15-avd_aarch64.xml"
|
|
||||||
arch: "aarch64"
|
|
||||||
- version: "android-15-avd_x86_64"
|
|
||||||
manifest: "android-15-avd_x86_64.xml"
|
|
||||||
arch: "x86_64"
|
|
||||||
with:
|
|
||||||
version_name: ${{ matrix.version }}
|
|
||||||
manifest_name: ${{ matrix.manifest }}
|
|
||||||
arch: ${{ matrix.arch }}
|
|
||||||
debug: true
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
name: Build Kernel - WSA
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["next"]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-wsa.yml"
|
|
||||||
- ".github/workflows/wsa-kernel.yml"
|
|
||||||
- "kernel/**"
|
|
||||||
pull_request:
|
|
||||||
branches: ["next"]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-kernel-wsa.yml"
|
|
||||||
- ".github/workflows/wsa-kernel.yml"
|
|
||||||
- "kernel/**"
|
|
||||||
workflow_call:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
arch: [x86_64, arm64]
|
|
||||||
version: ["5.15.94.2", "5.15.104.1", "5.15.104.2", "5.15.104.3", "5.15.104.4"]
|
|
||||||
uses: ./.github/workflows/wsa-kernel.yml
|
|
||||||
with:
|
|
||||||
arch: ${{ matrix.arch }}
|
|
||||||
version: ${{ matrix.version }}
|
|
||||||
|
|
||||||
check_build:
|
|
||||||
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
|
|
||||||
uses: ./.github/workflows/wsa-kernel.yml
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
arch: [x86_64, arm64]
|
|
||||||
with:
|
|
||||||
arch: ${{ matrix.arch }}
|
|
||||||
version: "5.15.104.4"
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
name: Build LKM for KernelSU-Next
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-lkm.yml"
|
|
||||||
pull_request:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/build-lkm.yml"
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
build-lkm:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- version: "android12-5.10"
|
|
||||||
sub_level: 236
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "android13-5.10"
|
|
||||||
sub_level: 236
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "android13-5.15"
|
|
||||||
sub_level: 180
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "android14-5.15"
|
|
||||||
sub_level: 180
|
|
||||||
os_patch_level: 2025-05
|
|
||||||
- version: "android14-6.1"
|
|
||||||
sub_level: 138
|
|
||||||
os_patch_level: 2025-06
|
|
||||||
- version: "android15-6.6"
|
|
||||||
sub_level: 89
|
|
||||||
os_patch_level: 2025-06
|
|
||||||
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
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
name: Build Manager CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/build-manager-ci.yml'
|
|
||||||
- 'manager/**'
|
|
||||||
- 'kernel/**'
|
|
||||||
- 'userspace/ksud_overlayfs**'
|
|
||||||
- 'userspace/ksud_magic/**'
|
|
||||||
- 'userspace/susfsd/**'
|
|
||||||
pull_request:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/build-manager-ci.yml'
|
|
||||||
- 'manager/**'
|
|
||||||
workflow_call:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-lkm:
|
|
||||||
uses: ./.github/workflows/build-lkm.yml
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
build-susfsd:
|
|
||||||
needs: build-lkm
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-latest
|
|
||||||
uses: ./.github/workflows/susfsd.yml
|
|
||||||
with:
|
|
||||||
os: ${{ matrix.os }}
|
|
||||||
|
|
||||||
build-ksud:
|
|
||||||
needs: build-susfsd
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- target: aarch64-linux-android
|
|
||||||
os: ubuntu-latest
|
|
||||||
- target: armv7-linux-androideabi
|
|
||||||
os: ubuntu-latest
|
|
||||||
- target: x86_64-linux-android
|
|
||||||
os: ubuntu-latest
|
|
||||||
uses: ./.github/workflows/ksud.yml
|
|
||||||
with:
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
os: ${{ matrix.os }}
|
|
||||||
|
|
||||||
build-manager:
|
|
||||||
needs: build-ksud
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./manager
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
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: Write Key
|
|
||||||
run: |
|
|
||||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
|
||||||
{
|
|
||||||
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'
|
|
||||||
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
|
|
||||||
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
|
|
||||||
echo KEYSTORE_FILE='key.jks'
|
|
||||||
} >> gradle.properties
|
|
||||||
echo ${{ secrets.KEYSTORE }} | base64 -d > key.jks
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: temurin
|
|
||||||
java-version: 21
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
|
|
||||||
- name: Download susfsd
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: susfsd-linux-android
|
|
||||||
path: .
|
|
||||||
|
|
||||||
- name: Copy susfsd to app jniLibs
|
|
||||||
run: |
|
|
||||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
|
||||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
|
||||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/x86_64
|
|
||||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
|
||||||
|
|
||||||
|
|
||||||
- name: Download arm64 ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-aarch64-linux-android
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Download arm ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-armv7-linux-androideabi
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Download x86_64 ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-x86_64-linux-android
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Copy ksud_overlayfs to app jniLibs
|
|
||||||
run: |
|
|
||||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/x86_64
|
|
||||||
|
|
||||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
|
||||||
|
|
||||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
|
||||||
|
|
||||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
|
||||||
|
|
||||||
- name: Download arm64 ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-aarch64-linux-android
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Download arm ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-armv7-linux-androideabi
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Download x86_64 ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-x86_64-linux-android
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Copy ksud_magic to app jniLibs
|
|
||||||
run: |
|
|
||||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/x86_64
|
|
||||||
|
|
||||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
|
||||||
|
|
||||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
|
||||||
|
|
||||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
|
||||||
|
|
||||||
- name: Build with Gradle
|
|
||||||
run: |
|
|
||||||
{
|
|
||||||
echo 'org.gradle.parallel=true'
|
|
||||||
echo 'org.gradle.vfs.watch=true'
|
|
||||||
echo 'org.gradle.jvmargs=-Xmx2048m'
|
|
||||||
echo 'android.native.buildOutput=verbose'
|
|
||||||
} >> gradle.properties
|
|
||||||
sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew clean assembleRelease
|
|
||||||
|
|
||||||
- name: Upload Build Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Manager
|
|
||||||
path: manager/app/build/outputs/apk/release/*.apk
|
|
||||||
|
|
||||||
- name: Upload Mappings
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Mappings
|
|
||||||
path: manager/app/build/outputs/mapping/release/
|
|
||||||
|
|
||||||
- name: Bot Session Cache
|
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
|
||||||
id: bot_session_cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: scripts/ksunextbot.session
|
|
||||||
key: ${{ runner.os }}-bot-session
|
|
||||||
|
|
||||||
- name: Upload to Telegram
|
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
|
||||||
env:
|
|
||||||
API_ID: ${{ secrets.API_ID }}
|
|
||||||
API_HASH: ${{ secrets.API_HASH }}
|
|
||||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
|
||||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
|
||||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
||||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
TITLE: CI Manager (TEST BUILD)
|
|
||||||
run: |
|
|
||||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
|
||||||
export VERSION=$(git rev-list --count HEAD)
|
|
||||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
|
||||||
pip3 install telethon
|
|
||||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
|
||||||
fi
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
name: Build Manager Spoofed
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/build-manager-ci.yml'
|
|
||||||
- 'manager/**'
|
|
||||||
- 'kernel/**'
|
|
||||||
- 'userspace/ksud_overlayfs**'
|
|
||||||
- 'userspace/ksud_magic/**'
|
|
||||||
- 'userspace/susfsd/**'
|
|
||||||
pull_request:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/build-manager-ci.yml'
|
|
||||||
- 'manager/**'
|
|
||||||
workflow_call:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 12 * * *" # 6 PM UTC+6 | 12 PM UTC
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-lkm:
|
|
||||||
uses: ./.github/workflows/build-lkm.yml
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
build-susfsd:
|
|
||||||
needs: build-lkm
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-latest
|
|
||||||
uses: ./.github/workflows/susfsd.yml
|
|
||||||
with:
|
|
||||||
os: ${{ matrix.os }}
|
|
||||||
|
|
||||||
build-ksud:
|
|
||||||
needs: build-susfsd
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- target: aarch64-linux-android
|
|
||||||
os: ubuntu-latest
|
|
||||||
- target: armv7-linux-androideabi
|
|
||||||
os: ubuntu-latest
|
|
||||||
- target: x86_64-linux-android
|
|
||||||
os: ubuntu-latest
|
|
||||||
uses: ./.github/workflows/ksud.yml
|
|
||||||
with:
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
os: ${{ matrix.os }}
|
|
||||||
|
|
||||||
build-manager:
|
|
||||||
needs: build-ksud
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./manager
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Spoof Package ID
|
|
||||||
run: |
|
|
||||||
chmod +x spoof
|
|
||||||
./spoof
|
|
||||||
|
|
||||||
- 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: Write Key
|
|
||||||
run: |
|
|
||||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
|
||||||
{
|
|
||||||
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'
|
|
||||||
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
|
|
||||||
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
|
|
||||||
echo KEYSTORE_FILE='key.jks'
|
|
||||||
} >> gradle.properties
|
|
||||||
echo ${{ secrets.KEYSTORE }} | base64 -d > key.jks
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: temurin
|
|
||||||
java-version: 21
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
|
|
||||||
- name: Download susfsd
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: susfsd-linux-android
|
|
||||||
path: .
|
|
||||||
|
|
||||||
- name: Copy susfsd to app jniLibs
|
|
||||||
run: |
|
|
||||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
|
||||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
|
||||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/x86_64
|
|
||||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
|
||||||
|
|
||||||
|
|
||||||
- name: Download arm64 ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-aarch64-linux-android
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Download arm ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-armv7-linux-androideabi
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Download x86_64 ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-x86_64-linux-android
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Copy ksud_overlayfs to app jniLibs
|
|
||||||
run: |
|
|
||||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
|
||||||
|
|
||||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
|
||||||
|
|
||||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
|
||||||
|
|
||||||
- name: Download arm64 ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-aarch64-linux-android
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Download arm ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-armv7-linux-androideabi
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Download x86_64 ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-x86_64-linux-android
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Copy ksud_magic to app jniLibs
|
|
||||||
run: |
|
|
||||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
|
||||||
|
|
||||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
|
||||||
|
|
||||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
|
||||||
|
|
||||||
- name: Build with Gradle
|
|
||||||
run: |
|
|
||||||
{
|
|
||||||
echo 'org.gradle.parallel=true'
|
|
||||||
echo 'org.gradle.vfs.watch=true'
|
|
||||||
echo 'org.gradle.jvmargs=-Xmx2048m'
|
|
||||||
echo 'android.native.buildOutput=verbose'
|
|
||||||
} >> gradle.properties
|
|
||||||
sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew clean assembleRelease
|
|
||||||
|
|
||||||
- name: Upload Build Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Manager
|
|
||||||
path: manager/app/build/outputs/apk/release/*.apk
|
|
||||||
|
|
||||||
- name: Upload Mappings
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Mappings
|
|
||||||
path: manager/app/build/outputs/mapping/release/
|
|
||||||
|
|
||||||
- name: Bot Session Cache
|
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
|
||||||
id: bot_session_cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: scripts/ksunextbot.session
|
|
||||||
key: ${{ runner.os }}-bot-session
|
|
||||||
|
|
||||||
- name: Upload to Telegram
|
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
|
||||||
env:
|
|
||||||
API_ID: ${{ secrets.API_ID }}
|
|
||||||
API_HASH: ${{ secrets.API_HASH }}
|
|
||||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
|
||||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
|
||||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
||||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
TITLE: CI Manager (SPOOFED BUILD)
|
|
||||||
run: |
|
|
||||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
|
||||||
export VERSION=$(git rev-list --count HEAD)
|
|
||||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
|
||||||
pip3 install telethon
|
|
||||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
|
||||||
fi
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
name: Build Manager
|
|
||||||
|
|
||||||
on:
|
|
||||||
# push:
|
|
||||||
# branches: [ "next" ]
|
|
||||||
# paths:
|
|
||||||
# - '.github/workflows/build-manager.yml'
|
|
||||||
# - 'manager/**'
|
|
||||||
# - 'kernel/**'
|
|
||||||
# - 'userspace/ksud_overlayfs**'
|
|
||||||
# - 'userspace/ksud_magic/**'
|
|
||||||
# - 'userspace/susfsd/**'
|
|
||||||
# pull_request:
|
|
||||||
# branches: [ "next" ]
|
|
||||||
# paths:
|
|
||||||
# - '.github/workflows/build-manager-ci.yml'
|
|
||||||
# - 'manager/**'
|
|
||||||
workflow_call:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-lkm:
|
|
||||||
uses: ./.github/workflows/build-lkm.yml
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
build-susfsd:
|
|
||||||
needs: build-lkm
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-latest
|
|
||||||
uses: ./.github/workflows/susfsd.yml
|
|
||||||
with:
|
|
||||||
os: ${{ matrix.os }}
|
|
||||||
|
|
||||||
build-ksud:
|
|
||||||
needs: build-susfsd
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- target: aarch64-linux-android
|
|
||||||
os: ubuntu-latest
|
|
||||||
- target: armv7-linux-androideabi
|
|
||||||
os: ubuntu-latest
|
|
||||||
- target: x86_64-linux-android
|
|
||||||
os: ubuntu-latest
|
|
||||||
uses: ./.github/workflows/ksud.yml
|
|
||||||
with:
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
os: ${{ matrix.os }}
|
|
||||||
|
|
||||||
build-manager:
|
|
||||||
needs: build-ksud
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./manager
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
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: Write Key
|
|
||||||
run: |
|
|
||||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
|
||||||
{
|
|
||||||
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'
|
|
||||||
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
|
|
||||||
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
|
|
||||||
echo KEYSTORE_FILE='key.jks'
|
|
||||||
} >> gradle.properties
|
|
||||||
echo ${{ secrets.KEYSTORE }} | base64 -d > key.jks
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: temurin
|
|
||||||
java-version: 21
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
|
|
||||||
- name: Download susfsd
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: susfsd-linux-android
|
|
||||||
path: .
|
|
||||||
|
|
||||||
- name: Copy susfsd to app jniLibs
|
|
||||||
run: |
|
|
||||||
mkdir -p app/src/main/jniLibs/arm64-v8a
|
|
||||||
cp -f ../arm64-v8a/susfsd ../manager/app/src/main/jniLibs/arm64-v8a/libsusfsd.so
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/armeabi-v7a
|
|
||||||
cp -f ../armeabi-v7a/susfsd ../manager/app/src/main/jniLibs/armeabi-v7a/libsusfsd.so
|
|
||||||
|
|
||||||
mkdir -p app/src/main/jniLibs/x86_64
|
|
||||||
cp -f ../x86_64/susfsd ../manager/app/src/main/jniLibs/x86_64/libsusfsd.so
|
|
||||||
|
|
||||||
|
|
||||||
- name: Download arm64 ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-aarch64-linux-android
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Download arm ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-armv7-linux-androideabi
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Download x86_64 ksud_overlayfs
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-x86_64-linux-android
|
|
||||||
path: ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Copy ksud_overlayfs to app jniLibs
|
|
||||||
run: |
|
|
||||||
cp -f ../ksud_overlayfs/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_overlayfs.so
|
|
||||||
|
|
||||||
cp -f ../ksud_overlayfs/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_overlayfs.so
|
|
||||||
|
|
||||||
cp -f ../ksud_overlayfs/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_overlayfs.so
|
|
||||||
|
|
||||||
- name: Download arm64 ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-aarch64-linux-android
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Download arm ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-armv7-linux-androideabi
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Download x86_64 ksud_magic
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-x86_64-linux-android
|
|
||||||
path: ksud_magic
|
|
||||||
|
|
||||||
- name: Copy ksud_magic to app jniLibs
|
|
||||||
run: |
|
|
||||||
cp -f ../ksud_magic/aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud_magic.so
|
|
||||||
|
|
||||||
cp -f ../ksud_magic/armv7-linux-androideabi/release/ksud ../manager/app/src/main/jniLibs/armeabi-v7a/libksud_magic.so
|
|
||||||
|
|
||||||
cp -f ../ksud_magic/x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud_magic.so
|
|
||||||
|
|
||||||
- name: Build with Gradle
|
|
||||||
run: |
|
|
||||||
{
|
|
||||||
echo 'org.gradle.parallel=true'
|
|
||||||
echo 'org.gradle.vfs.watch=true'
|
|
||||||
echo 'org.gradle.jvmargs=-Xmx2048m'
|
|
||||||
echo 'android.native.buildOutput=verbose'
|
|
||||||
} >> gradle.properties
|
|
||||||
sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew clean assembleRelease
|
|
||||||
|
|
||||||
- name: Upload Build Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Manager
|
|
||||||
path: manager/app/build/outputs/apk/release/*.apk
|
|
||||||
|
|
||||||
- name: Upload Mappings
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Mappings
|
|
||||||
path: manager/app/build/outputs/mapping/release/
|
|
||||||
|
|
||||||
- name: Bot Session Cache
|
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
|
||||||
id: bot_session_cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: scripts/ksunextbot.session
|
|
||||||
key: ${{ runner.os }}-bot-session
|
|
||||||
|
|
||||||
- name: Upload to Telegram
|
|
||||||
if: steps.need_upload.outputs.UPLOAD == 'true'
|
|
||||||
env:
|
|
||||||
API_ID: ${{ secrets.API_ID }}
|
|
||||||
API_HASH: ${{ secrets.API_HASH }}
|
|
||||||
CHAT_ID: ${{ secrets.CHAT_ID }}
|
|
||||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
|
|
||||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
||||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
TITLE: Release Manager (STABLE BUILD)
|
|
||||||
run: |
|
|
||||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
|
||||||
export VERSION=$(git rev-list --count HEAD)
|
|
||||||
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
|
|
||||||
pip3 install telethon
|
|
||||||
python3 $GITHUB_WORKSPACE/scripts/ksunextbot.py $APK
|
|
||||||
fi
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
name: Build SU
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/build-su.yml'
|
|
||||||
- 'userspace/su/**'
|
|
||||||
- 'scripts/ksunextbot.py'
|
|
||||||
pull_request:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- 'userspace/su/**'
|
|
||||||
jobs:
|
|
||||||
build-su:
|
|
||||||
name: Build userspace su
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
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: Build su
|
|
||||||
working-directory: ./userspace/su
|
|
||||||
run: $ANDROID_NDK/ndk-build
|
|
||||||
- name: Upload a Build Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: su
|
|
||||||
path: ./userspace/su/libs
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
name: Clippy check
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- next
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/clippy.yml'
|
|
||||||
- 'userspace/ksud_magic/**'
|
|
||||||
- 'userspace/ksud_overlayfs/**'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- next
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/clippy.yml'
|
|
||||||
- 'userspace/ksud_magic/**'
|
|
||||||
- 'userspace/ksud_overlayfs/**'
|
|
||||||
|
|
||||||
env:
|
|
||||||
RUSTFLAGS: '-Dwarnings'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
clippy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Rust
|
|
||||||
run: rustup update stable
|
|
||||||
|
|
||||||
- name: Setup Cross
|
|
||||||
run: RUSTFLAGS="" cargo install cross
|
|
||||||
|
|
||||||
- name: Cache ksud_overlayfs
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: userspace/ksud_overlayfs
|
|
||||||
|
|
||||||
- name: Cache ksud_magic
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: userspace/ksud_magic
|
|
||||||
|
|
||||||
- name: Run Clippy
|
|
||||||
run: |
|
|
||||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target aarch64-linux-android --release
|
|
||||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target aarch64-linux-android --release
|
|
||||||
|
|
||||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target armv7-linux-androideabi --release
|
|
||||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target armv7-linux-androideabi --release
|
|
||||||
|
|
||||||
cross clippy --manifest-path userspace/ksud_magic/Cargo.toml --target x86_64-linux-android --release
|
|
||||||
cross clippy --manifest-path userspace/ksud_overlayfs/Cargo.toml --target x86_64-linux-android --release
|
|
||||||
@@ -1,261 +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-Next
|
|
||||||
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.16
|
|
||||||
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-Next
|
|
||||||
env:
|
|
||||||
PATCH_PATH: ${{ inputs.patch_path }}
|
|
||||||
IS_DEBUG_KERNEL: ${{ inputs.debug }}
|
|
||||||
run: |
|
|
||||||
cd $GITHUB_WORKSPACE/android-kernel
|
|
||||||
echo "[+] KernelSU-Next setup"
|
|
||||||
GKI_ROOT=$(pwd)
|
|
||||||
echo "[+] GKI_ROOT: $GKI_ROOT"
|
|
||||||
echo "[+] Copy KernelSU-Next driver to $GKI_ROOT/common/drivers"
|
|
||||||
ln -sf $GITHUB_WORKSPACE/KernelSU-Next/kernel $GKI_ROOT/common/drivers/kernelsu
|
|
||||||
echo "[+] Add KernelSU-Next 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-Next/kernel/Makefile
|
|
||||||
fi
|
|
||||||
repo status
|
|
||||||
echo "[+] KernelSU-Next 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-Next
|
|
||||||
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-Next 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: |
|
|
||||||
if [ -f ./common/BUILD.bazel ]; then
|
|
||||||
sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' ./common/BUILD.bazel
|
|
||||||
fi
|
|
||||||
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
|
|
||||||
git config --global user.email "bot@kernelsu.org"
|
|
||||||
git config --global user.name "KernelSU-NextBot"
|
|
||||||
cd common/ && git add -A && git commit -a -m "Add KernelSU-Next"
|
|
||||||
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
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
name: Build ksud
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
target:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
os:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: ubuntu-latest
|
|
||||||
pack_lkm:
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
use_cache:
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ${{ inputs.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download Artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- name: Prepare LKM Files
|
|
||||||
if: ${{ inputs.pack_lkm }}
|
|
||||||
run: |
|
|
||||||
cp android*-lkm/*_kernelsu.ko ./userspace/ksud_overlayfs/bin/aarch64/
|
|
||||||
cp android*-lkm/*_kernelsu.ko ./userspace/ksud_magic/bin/aarch64/
|
|
||||||
|
|
||||||
- name: Import susfsd Libraries
|
|
||||||
run: |
|
|
||||||
cp susfsd-linux-android/arm64-v8a/susfsd ./userspace/ksud_overlayfs/bin/aarch64/
|
|
||||||
cp susfsd-linux-android/arm64-v8a/susfsd ./userspace/ksud_magic/bin/aarch64/
|
|
||||||
cp susfsd-linux-android/armeabi-v7a/susfsd ./userspace/ksud_overlayfs/bin/arm/
|
|
||||||
cp susfsd-linux-android/armeabi-v7a/susfsd ./userspace/ksud_magic/bin/arm/
|
|
||||||
cp susfsd-linux-android/x86_64/susfsd ./userspace/ksud_overlayfs/bin/x86_64/
|
|
||||||
cp susfsd-linux-android/x86_64/susfsd ./userspace/ksud_magic/bin/x86_64/
|
|
||||||
|
|
||||||
- name: Setup Rust
|
|
||||||
run: |
|
|
||||||
rustup update stable
|
|
||||||
rustup target add x86_64-apple-darwin
|
|
||||||
rustup target add aarch64-apple-darwin
|
|
||||||
|
|
||||||
- name: Cache ksud_overlayfs
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: userspace/ksud_overlayfs
|
|
||||||
cache-targets: false
|
|
||||||
|
|
||||||
- name: Cache ksud_magic
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: userspace/ksud_magic
|
|
||||||
cache-targets: false
|
|
||||||
|
|
||||||
- name: Setup Cross
|
|
||||||
run: |
|
|
||||||
RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross --rev 66845c1 --force
|
|
||||||
|
|
||||||
- name: Build ksud
|
|
||||||
run: |
|
|
||||||
CROSS_NO_WARNINGS=0 cross build --target ${{ inputs.target }} --release --manifest-path ./userspace/ksud_overlayfs/Cargo.toml
|
|
||||||
CROSS_NO_WARNINGS=0 cross build --target ${{ inputs.target }} --release --manifest-path ./userspace/ksud_magic/Cargo.toml
|
|
||||||
|
|
||||||
- name: Upload ksud_overlayfs artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_overlayfs-${{ inputs.target }}
|
|
||||||
path: userspace/ksud_overlayfs/target/**/release/ksud*
|
|
||||||
|
|
||||||
- name: Upload ksud_magic artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ksud_magic-${{ inputs.target }}
|
|
||||||
path: userspace/ksud_magic/target/**/release/ksud*
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
name: Release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-manager:
|
|
||||||
uses: ./.github/workflows/build-manager.yml
|
|
||||||
secrets: inherit
|
|
||||||
build-a12-kernel:
|
|
||||||
uses: ./.github/workflows/build-kernel-a12.yml
|
|
||||||
secrets: inherit
|
|
||||||
build-a13-kernel:
|
|
||||||
uses: ./.github/workflows/build-kernel-a13.yml
|
|
||||||
secrets: inherit
|
|
||||||
build-a14-kernel:
|
|
||||||
uses: ./.github/workflows/build-kernel-a14.yml
|
|
||||||
secrets: inherit
|
|
||||||
build-a15-kernel:
|
|
||||||
uses: ./.github/workflows/build-kernel-a15.yml
|
|
||||||
secrets: inherit
|
|
||||||
build-wsa-kernel:
|
|
||||||
uses: ./.github/workflows/build-kernel-wsa.yml
|
|
||||||
secrets: inherit
|
|
||||||
build-arcvm-kernel:
|
|
||||||
uses: ./.github/workflows/build-kernel-arcvm.yml
|
|
||||||
secrets: inherit
|
|
||||||
release:
|
|
||||||
needs:
|
|
||||||
- build-manager
|
|
||||||
- build-a12-kernel
|
|
||||||
- build-a13-kernel
|
|
||||||
- build-a14-kernel
|
|
||||||
- build-a15-kernel
|
|
||||||
- build-wsa-kernel
|
|
||||||
- build-arcvm-kernel
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
- name: Zip AnyKernel3
|
|
||||||
run: |
|
|
||||||
for dir in AnyKernel3-*; do
|
|
||||||
if [ -d "$dir" ]; then
|
|
||||||
echo "----- Zip $dir -----"
|
|
||||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Zip WSA kernel
|
|
||||||
run: |
|
|
||||||
for dir in kernel-WSA-*; do
|
|
||||||
if [ -d "$dir" ]; then
|
|
||||||
echo "------ Zip $dir ----------"
|
|
||||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Zip ChromeOS ARCVM kernel
|
|
||||||
run: |
|
|
||||||
for dir in kernel-ARCVM-*; do
|
|
||||||
if [ -d "$dir" ]; then
|
|
||||||
echo "------ Zip $dir ----------"
|
|
||||||
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Display structure of downloaded files
|
|
||||||
run: ls -R
|
|
||||||
|
|
||||||
- name: release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
Manager/*.apk
|
|
||||||
android*-lkm/*_kernelsu.ko
|
|
||||||
AnyKernel3-*.zip
|
|
||||||
boot-images-*/Image-*/*.img.gz
|
|
||||||
kernel-WSA*.zip
|
|
||||||
kernel-ARCVM*.zip
|
|
||||||
ksud_magic*.zip
|
|
||||||
ksud_overlayfs*.zip
|
|
||||||
susfsd*.zip
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
name: Rustfmt check
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'next'
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/rustfmt.yml'
|
|
||||||
- 'userspace/ksud_magic/**'
|
|
||||||
- 'userspace/ksud_overlayfs/**'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- 'next'
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/rustfmt.yml'
|
|
||||||
- 'userspace/ksud_magic/**'
|
|
||||||
- 'userspace/ksud_overlayfs/**'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
format:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
|
||||||
with:
|
|
||||||
components: rustfmt
|
|
||||||
|
|
||||||
- uses: LoliGothick/rustfmt-check@master
|
|
||||||
with:
|
|
||||||
token: ${{ github.token }}
|
|
||||||
working-directory: userspace/ksud_magic
|
|
||||||
|
|
||||||
- uses: LoliGothick/rustfmt-check@master
|
|
||||||
with:
|
|
||||||
token: ${{ github.token }}
|
|
||||||
working-directory: userspace/ksud_overlayfs
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
name: ShellCheck
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'next'
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/shellcheck.yml'
|
|
||||||
- '**/*.sh'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- 'next'
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/shellcheck.yml'
|
|
||||||
- '**/*.sh'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
shellcheck:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run ShellCheck
|
|
||||||
uses: ludeeus/action-shellcheck@2.0.0
|
|
||||||
with:
|
|
||||||
ignore_names: gradlew
|
|
||||||
ignore_paths: ./userspace/ksud_magic/src/installer.sh ./userspace/ksud_overlayfs/src/installer.sh
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
name: Build susfsd aarch64
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "next" ]
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/susfsd.yml'
|
|
||||||
- 'userspace/susfsd/**'
|
|
||||||
workflow_dispatch:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
os:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: ubuntu-latest
|
|
||||||
jobs:
|
|
||||||
build-susfs:
|
|
||||||
name: Build userspace susfsd
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Build susfsd
|
|
||||||
working-directory: ./userspace/susfsd
|
|
||||||
run: $ANDROID_NDK/ndk-build
|
|
||||||
- name: Upload Build Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: susfsd-linux-android
|
|
||||||
path: ./userspace/susfsd/libs
|
|
||||||
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
name: Build Kernel - WSA
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
arch:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
description: >
|
|
||||||
Build arch: x86_64 / arm64
|
|
||||||
version:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
description: >
|
|
||||||
Build version
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
env:
|
|
||||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
|
||||||
CCACHE_NOHASHDIR: "true"
|
|
||||||
CCACHE_HARDLINK: "true"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Install Build Tools
|
|
||||||
uses: awalsh128/cache-apt-pkgs-action@v1
|
|
||||||
with:
|
|
||||||
packages: bc bison build-essential flex libelf-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu gzip ccache
|
|
||||||
version: 1.0
|
|
||||||
|
|
||||||
- name: Cache LLVM
|
|
||||||
id: cache-llvm
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ./llvm
|
|
||||||
key: llvm-12.0.1
|
|
||||||
|
|
||||||
- name: Setup LLVM
|
|
||||||
uses: KyleMayes/install-llvm-action@v1
|
|
||||||
with:
|
|
||||||
version: "12.0.1"
|
|
||||||
force-version: true
|
|
||||||
ubuntu-version: "16.04"
|
|
||||||
cached: ${{ steps.cache-llvm.outputs.cache-hit }}
|
|
||||||
|
|
||||||
- name: Checkout KernelSU-Next
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
path: KernelSU-Next
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup kernel source
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: microsoft/WSA-Linux-Kernel
|
|
||||||
ref: android-lts/latte-2/${{ inputs.version }}
|
|
||||||
path: WSA-Linux-Kernel
|
|
||||||
|
|
||||||
- name: Setup Ccache
|
|
||||||
uses: hendrikmuhs/ccache-action@v1
|
|
||||||
with:
|
|
||||||
key: WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
|
|
||||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
||||||
max-size: 2G
|
|
||||||
|
|
||||||
- name: Setup KernelSU-Next
|
|
||||||
working-directory: WSA-Linux-Kernel
|
|
||||||
run: |
|
|
||||||
echo "[+] KernelSU-Next setup"
|
|
||||||
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
|
|
||||||
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
|
|
||||||
echo "[+] Copy KernelSU-Next driver to $KERNEL_ROOT/drivers"
|
|
||||||
ln -sf $GITHUB_WORKSPACE/KernelSU-Next/kernel $KERNEL_ROOT/drivers/kernelsu
|
|
||||||
echo "[+] Add KernelSU-Next driver to Makefile"
|
|
||||||
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
|
|
||||||
DRIVER_KCONFIG=$KERNEL_ROOT/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-Next patches"
|
|
||||||
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU-Next/.github/patches/5.15/*.patch || echo "[-] No patch found"
|
|
||||||
echo "[+] KernelSU-Next setup done."
|
|
||||||
cd $GITHUB_WORKSPACE/KernelSU-Next
|
|
||||||
VERSION=$(($(git rev-list --count HEAD) + 10200))
|
|
||||||
echo "VERSION: $VERSION"
|
|
||||||
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build Kernel
|
|
||||||
working-directory: WSA-Linux-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
|
|
||||||
declare -A ARCH_MAP=(["x86_64"]="x64" ["arm64"]="arm64")
|
|
||||||
cp configs/wsa/config-wsa-${ARCH_MAP[${{ inputs.arch }}]} .config
|
|
||||||
make olddefconfig
|
|
||||||
declare -A FILE_NAME=(["x86_64"]="bzImage" ["arm64"]="Image")
|
|
||||||
make -j`nproc` LLVM=1 ARCH=${{ inputs.arch }} $(if [ "${{ inputs.arch }}" == "arm64" ]; then echo CROSS_COMPILE=aarch64-linux-gnu; fi) ${FILE_NAME[${{ inputs.arch }}]} CCACHE="/usr/bin/ccache"
|
|
||||||
declare -A ARCH_MAP_FILE=(["x86_64"]="x86" ["arm64"]="arm64")
|
|
||||||
echo "file_path=WSA-Linux-Kernel/arch/${ARCH_MAP_FILE[${{ inputs.arch }}]}/boot/${FILE_NAME[${{ inputs.arch }}]}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Upload kernel-${{ inputs.arch }}-${{ inputs.version }}
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: kernel-WSA-${{ inputs.arch }}-${{ inputs.version }}
|
|
||||||
path: "${{ env.file_path }}"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Reporting Security Issues
|
|
||||||
|
|
||||||
The KernelSU team and community take security bugs in KernelSU seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
|
||||||
|
|
||||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/tiann/KernelSU/security/advisories/new) tab, or you can mailto [weishu](mailto:twsxtd@gmail.com) directly.
|
|
||||||
|
|
||||||
The KernelSU team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,2 +0,0 @@
|
|||||||
bundles:
|
|
||||||
- 8
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
**English** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Українська](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md) | [Español](README_ES.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
|
||||||
|
|
||||||
<h2>KernelSU Next</h2>
|
|
||||||
<p><strong>A kernel-based root solution for Android devices.</strong></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
|
||||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
|
||||||
</a>
|
|
||||||
<a href="/LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
|
||||||
</a>
|
|
||||||
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/kernelsu-next"><img src="https://badges.crowdin.net/kernelsu-next/localized.svg"></a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Features
|
|
||||||
|
|
||||||
- Kernel-based `su` and root access management.
|
|
||||||
- Module system based on [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) and [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
- [App Profile](https://kernelsu.org/guide/app-profile.html): Limit root privileges per app.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Compatibility
|
|
||||||
|
|
||||||
KernelSU Next supports Android kernels from **4.4 up to 6.6**:
|
|
||||||
|
|
||||||
| Kernel version | Support notes |
|
|
||||||
|----------------------|-------------------------------------------------------------------------|
|
|
||||||
| 5.10+ (GKI 2.0) | Supports pre-built images and LKM/KMI |
|
|
||||||
| 4.19 – 5.4 (GKI 1.0) | Requires KernelSU driver built-in |
|
|
||||||
| < 4.14 (EOL) | Requires KernelSU driver (3.18+ is experimental and may need backports) |
|
|
||||||
|
|
||||||
**Supported architectures:** `arm64-v8a`, `armeabi-v7a` and `x86_64`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Installation
|
|
||||||
|
|
||||||
Please refer to the [Installation](https://kernelsu-next.github.io/webpage/pages/installation.html) guide for setup instructions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Security
|
|
||||||
|
|
||||||
To report security issues, please see [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 License
|
|
||||||
|
|
||||||
- **`/kernel` directory:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- **All other files:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💸 Donations
|
|
||||||
|
|
||||||
If you’d like to support the project:
|
|
||||||
|
|
||||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
|
||||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
|
||||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 Credits
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Concept inspiration
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Core root implementation
|
|
||||||
- [Genuine](https://github.com/brevent/genuine/) – APK v2 signature validation
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Rootkit techniques
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU) – The original base that made KernelSU Next possible
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 to 5ec1cff for keeping KernelSU alive
|
|
||||||
- [Crowdin Translators](https://crowdin.com/project/kernelsu-next/members) – 💬 Thanks to everyone for helping make KernelSU Next multi-lingual!
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | **Български** | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="лого">
|
|
||||||
|
|
||||||
Ядрено решение за root достъп за Android устройства.
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## Възможности
|
|
||||||
|
|
||||||
1. Управление на `su` и root достъп на ядрено ниво
|
|
||||||
2. Система за модули базирана на [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)
|
|
||||||
3. [Профили за приложения](https://kernelsu.org/guide/app-profile.html): Ограничаване на root права за конкретни приложения
|
|
||||||
|
|
||||||
## Съвместимост
|
|
||||||
|
|
||||||
KernelSU Next официално поддържа повечето Android ядра от версия 4.4 до 6.6:
|
|
||||||
- Ядра GKI 2.0 (5.10+) могат да използват предварително компилирани изображения и LKM/KMI
|
|
||||||
- Ядра GKI 1.0 (4.19 - 5.4) изискват прекомпилиране с драйвера на KernelSU
|
|
||||||
- Остарели ядра (<4.14) също изискват прекомпилиране (3.18+ е експериментална поддръжка)
|
|
||||||
|
|
||||||
В момента се поддържа само архитектурата `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
|
||||||
|
|
||||||
## Инсталация
|
|
||||||
|
|
||||||
- [Инструкции за инсталиране](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## Сигурност
|
|
||||||
|
|
||||||
За докладване на уязвимости вижте [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
## Лиценз
|
|
||||||
|
|
||||||
- Файловете в директорията `kernel` са [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
- Всички останали файлове са [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
|
||||||
|
|
||||||
## Дарения
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
|
||||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
|
||||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
|
||||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
|
||||||
|
|
||||||
## Благодарности
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Идеята за KernelSU
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): Мощният root инструмент
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): Валидация на APK подписи v2
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Rootkit техники
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): Благодарности към tiann за създаването на KernelSU
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff за спасяването на KernelSU
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
[English](README.md) | **简体中文** | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
|
||||||
|
|
||||||
<h2>KernelSU Next</h2>
|
|
||||||
<p><strong>安卓设备基于内核的 Root 方案</strong></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
|
||||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
|
||||||
</a>
|
|
||||||
<a href="/LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 特性
|
|
||||||
|
|
||||||
- 基于内核的 `su` 和超级用户权限管理
|
|
||||||
- 动态挂载系统基于 **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** 以及 **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)**
|
|
||||||
- [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 权限关进笼子里
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 兼容性
|
|
||||||
|
|
||||||
KernelSU Next 支持从 **4.4 到 6.6** 的大多数安卓内核
|
|
||||||
|
|
||||||
| 内核版本 | 支持情况 |
|
|
||||||
|----------------|---------------|
|
|
||||||
| 5.10+ (GKI 2.0) | 可运行预置镜像和 LKM/KMI |
|
|
||||||
| 4.19 – 5.4 (GKI 1.0) | 需要使用 KernelSU 内核驱动重新编译 |
|
|
||||||
| <4.14 (EOL) | 需要使用 KernelSU 内核驱动重新编译(3.18+ 的版本处于试验阶段,可能需要进行回溯移植) |
|
|
||||||
|
|
||||||
**支持的架构:**
|
|
||||||
`arm64-v8a`、`armeabi-v7a`、`x86_64`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 安装
|
|
||||||
|
|
||||||
请遵循该[安装说明](https://kernelsu-next.github.io/webpage/zh_CN/pages/installation.html)进行操作。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 安全性
|
|
||||||
|
|
||||||
有关报告 KernelSU Next 漏洞的信息,请参阅 [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 许可
|
|
||||||
|
|
||||||
- **目录 `/kernel` 下所有文件**为 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
- **`/kernel` 目录以外的其他部分**均为 [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💸 捐赠
|
|
||||||
|
|
||||||
如果你喜欢这个项目还请支持:
|
|
||||||
|
|
||||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
|
||||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
|
||||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 鸣谢
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的灵感.
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk):强大的 Root 工具.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/):APK v2 签名验证。
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 Rootkit 技巧。
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU):感谢 tiann,否则 KernelSU Next 根本不会存在。
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs):💜 5ec1cff 为了拯救 KernelSU!
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
**English** | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Українська](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md) | [Español](README_ES.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
|
||||||
|
|
||||||
<h2>KernelSU Next</h2>
|
|
||||||
<p><strong>Una solución de root basada en el kernel para tus dispositivos Android.</strong></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
|
||||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
|
||||||
</a>
|
|
||||||
<a href="/LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Características
|
|
||||||
|
|
||||||
- `su` y gestión de acceso root basados en el kernel.
|
|
||||||
- Sistema de módulos basado en [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) y [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
- [Perfil de Aplicación](https://kernelsu.org/guide/app-profile.html): Limita los privilegios de root por aplicación.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Compatibilidad
|
|
||||||
|
|
||||||
KernelSU Next es compatible con kernels de Android desde la versión **4.4 hasta la 6.6**:
|
|
||||||
|
|
||||||
| Kernel version | Support notes |
|
|
||||||
|----------------------|-----------------------------------------------------------------------------------|
|
|
||||||
| 5.10+ (GKI 2.0) | Admite imágenes precompiladas y LKM/KMI |
|
|
||||||
| 4.19 – 5.4 (GKI 1.0) | Requiere que el driver de KernelSU esté integrado |
|
|
||||||
| < 4.14 (EOL) | Requiere el driver de KernelSU (3.18+ es experimental y puede necesitar backports |
|
|
||||||
|
|
||||||
**Arquitecturas compatibles: ** `arm64-v8a`, `armeabi-v7a` y `x86_64`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Instalación
|
|
||||||
|
|
||||||
Por favor, consulta la guía de [Instalación](https://kernelsu-next.github.io/webpage/pages/installation.html) para ver las instrucciones de configuración.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Seguridad
|
|
||||||
|
|
||||||
Para informar sobre problemas de seguridad, por favor, consulta [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 Licencia
|
|
||||||
|
|
||||||
- **Directorio `/kernel`:** [Solo-GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- **Todos los demás archivos:** [GPL-3.0-o-superior](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💸 Donaciones
|
|
||||||
|
|
||||||
Si te gustaría apoyar el proyecto:
|
|
||||||
|
|
||||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
|
||||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
|
||||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 Créditos
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Inspiración para el concepto
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Implementación principal del root
|
|
||||||
- [Genuine](https://github.com/brevent/genuine/) – Validación de la firma v2 de los APK
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Técnicas de rootkit
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU) – La base original que hizo posible KernelSU Next
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 a 5ec1cff por mantener vivo KernelSU
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | **Français** | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
|
||||||
|
|
||||||
Une solution root basée sur le noyau pour les appareils Android.
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## Fonctionnalités
|
|
||||||
|
|
||||||
1. Gestion des accès root et de la commande `su` basée sur le noyau.
|
|
||||||
2. Système de modules basé sur le système de montage dynamique [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
3. [Profil d'application](https://kernelsu.org/guide/app-profile.html) : Enfermez la puissance du root dans une cage.
|
|
||||||
|
|
||||||
## État de compatibilité
|
|
||||||
|
|
||||||
KernelSU Next prend officiellement en charge la plupart des noyaux Android de la version 4.4 à la version 6.6.
|
|
||||||
- Les noyaux GKI 2.0 (5.10+) peuvent exécuter des images pré-construites et des modules LKM/KMI.
|
|
||||||
- Les noyaux GKI 1.0 (4.19 - 5.4) doivent être reconstruits avec le pilote KernelSU.
|
|
||||||
- Les noyaux EOL (<4.14) doivent également être reconstruits avec le pilote KernelSU (3.18+ est expérimental et peut nécessiter des rétroportages fonctionnels).
|
|
||||||
|
|
||||||
Actuellement, seul `arm64-v8a`, `armeabi-v7a` & `x86_64` est pris en charge.
|
|
||||||
|
|
||||||
## Utilisation
|
|
||||||
|
|
||||||
- [Instructions d'installation](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## Sécurité
|
|
||||||
|
|
||||||
Pour signaler des vulnérabilités de sécurité dans KernelSU, consultez [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
## Licence
|
|
||||||
|
|
||||||
- Les fichiers du répertoire `kernel` sont sous licence [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- Toutes les autres parties, sauf le répertoire `kernel`, sont sous licence [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
## Crédits
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) : L'idée de KernelSU.
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk) : L'outil root puissant.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/) : Validation de signature APK v2.
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) : Quelques techniques de rootkit.
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU) : Merci à tiann, sans qui KernelSU Next n'existerait même pas.
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) : 💜 5ec1cff pour avoir sauvé KernelSU !
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | **Bahasa Indonesia** | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
|
||||||
|
|
||||||
Sebuah solusi root berbasis Kernel untuk perangkat Android.
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## Fitur
|
|
||||||
|
|
||||||
1. Akses root dan manajemen `su` berbasis Kernel.
|
|
||||||
2. Sistem modul berbasis sistem mount dinamis [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
3. [Profil Aplikasi](https://kernelsu.org/guide/app-profile.html): Mengunci kekuatan root dalam sebuah kandang.
|
|
||||||
|
|
||||||
## Status Kompatibilitas
|
|
||||||
|
|
||||||
KernelSU Next secara resmi mendukung sebagian besar kernel Android mulai dari 4.4 hingga 6.6.
|
|
||||||
- Kernel GKI 2.0 (5.10+) dapat menjalankan gambar yang telah dibangun sebelumnya dan LKM/KMI.
|
|
||||||
- Kernel GKI 1.0 (4.19 - 5.4) perlu dibangun ulang dengan driver KernelSU.
|
|
||||||
- Kernel EOL (<4.14) juga perlu dibangun ulang dengan driver KernelSU (3.18+ bersifat eksperimental dan mungkin memerlukan beberapa backport fungsi).
|
|
||||||
|
|
||||||
Saat ini, hanya `arm64-v8a`, `armeabi-v7a` & `x86_64` yang didukung.
|
|
||||||
|
|
||||||
## Penggunaan
|
|
||||||
|
|
||||||
- [Petunjuk instalasi](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## Keamanan
|
|
||||||
|
|
||||||
Untuk informasi tentang melaporkan kerentanannya di KernelSU, lihat [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
## Lisensi
|
|
||||||
|
|
||||||
- File di bawah direktori `kernel` menggunakan lisensi [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- Semua bagian lain kecuali direktori `kernel` menggunakan lisensi [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
## Kredit
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Ide KernelSU.
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): Alat root yang kuat.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): Validasi tanda tangan APK v2.
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Beberapa keterampilan rootkit.
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): Terima kasih kepada tiann, jika tidak, KernelSU Next bahkan tidak akan ada.
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff karena menyelamatkan KernelSU!
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | **Italiano** | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
|
||||||
|
|
||||||
Una soluzione root basata sul kernel per dispositivi Android.
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## Caratteristiche
|
|
||||||
|
|
||||||
1. Gestione degli accessi `su` e root basata sul kernel.
|
|
||||||
2. Sistema modulare basato sul sistema di montaggio dinamico [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Rinchiudi il potere della radice in una gabbia.
|
|
||||||
|
|
||||||
## Stato compatibilità
|
|
||||||
|
|
||||||
KernelSU Next supporta ufficialmente la maggior parte dei kernel Android dalla versione 4.4 alla 6.6.
|
|
||||||
- I kernel GKI 2.0 (5.10+) possono eseguire immagini precostruite e LKM/KMI.
|
|
||||||
- I kernel GKI 1.0 (4.19 - 5.4) devono essere ricostruiti con il driver KernelSU.
|
|
||||||
- Anche i kernel EOL (<4.14) devono essere ricostruiti con il driver KernelSU (la versione 3.18+ è sperimentale e potrebbe richiedere alcuni backport di funzioni).
|
|
||||||
|
|
||||||
Attualmente è supportata solo l'architettura `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
|
||||||
|
|
||||||
## Utilizzo
|
|
||||||
|
|
||||||
- [Istruzioni per l'installazione](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
Per informazioni sulla segnalazione delle vulnerabilità di sicurezza in KernelSU, vedere [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
## Licenza
|
|
||||||
|
|
||||||
- I file nella directory `kernel` sono [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- Tutte le altre parti eccetto la directory `kernel` sono [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
## Donazioni
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
|
||||||
|
|
||||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
|
||||||
|
|
||||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
|
||||||
|
|
||||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
|
||||||
|
|
||||||
## Crediti
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): L'idea di KernelSU.
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): Il potente strumento di root.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): Convalida della firma APK v2.
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Alcune competenze sui rootkit.
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): Grazie a tiann, altrimenti KernelSU Next non esisterebbe nemmeno.
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff per aver salvato KernelSU!
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | **日本語**
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
|
||||||
|
|
||||||
Android デバイス用のカーネルベースな root ソリューション。
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## 機能
|
|
||||||
|
|
||||||
1. カーネルベースの `su` および root アクセスの管理。
|
|
||||||
2. 動的マウントシステム [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) をベースとしたモジュールシステム。
|
|
||||||
3. [アプリプロファイル](https://kernelsu.org/guide/app-profile.html): root 権限をケージに閉じ込めます。
|
|
||||||
|
|
||||||
## 互換性の状態
|
|
||||||
|
|
||||||
KernelSU Next は 4.4 から 6.6 までのほとんどの Android カーネルを公式でサポートしています。
|
|
||||||
- GKI 2.0 (5.10 以降) のカーネルはビルド済みイメージで LKM/KMI を実行できます。
|
|
||||||
- GKI 1.0 (4.19 - 5.4) のカーネルは、KernelSU ドライバを使用してビルドする必要があります。
|
|
||||||
- EOL (4.14 未満) のカーネルも KernelSU ドライバを使用して再ビルドする必要があります (3.18 以降は実験中の段階であり、一部の関数のバックポートが必要になる場合があります)。
|
|
||||||
|
|
||||||
現在 `arm64-v8a`, `armeabi-v7a` & `x86_64` アーキテクチャのみをサポートしています。
|
|
||||||
|
|
||||||
## 使い方
|
|
||||||
|
|
||||||
- [インストール手順](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## セキュリティ
|
|
||||||
|
|
||||||
KernelSU のセキュリティ脆弱性の報告については [SECURITY.md](/SECURITY.md) を参照してください。
|
|
||||||
|
|
||||||
## ライセンス
|
|
||||||
|
|
||||||
- `kernel` ディレクトリ内のファイルは [GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.ja.html) のみライセンス下にあります。
|
|
||||||
- `kernel` ディレクトリを除くその他すべての部分は [GPL-3.0 またはそれ以降](https://www.gnu.org/licenses/gpl-3.0.html) のライセンス下にあります。
|
|
||||||
|
|
||||||
## 寄付
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
|
||||||
|
|
||||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
|
||||||
|
|
||||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
|
||||||
|
|
||||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
|
||||||
|
|
||||||
## クレジット
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU のアイデアを考案。
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): パワフルな root ツール。
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): APK v2 署名認証。
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): いくつかの rootkit スキル。
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): tiann に感謝を申し上げます。これが存在しなければ KernelSU Next は存在しませんでした。
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff へ KernelSU を救ってくれてありがとう!
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | **한국어** | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
|
||||||
|
|
||||||
안드로이드 기기들을 위한 커널 기반 루팅 솔루션입니다.
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## 기능
|
|
||||||
|
|
||||||
1. 커널 기반 `su` 및 루트 권한 관리
|
|
||||||
2. 동적 마운트 시스템 기반 모듈 시스템 [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): 루트 권한 제한
|
|
||||||
|
|
||||||
## 호환 상태
|
|
||||||
|
|
||||||
KernelSU Next는 공식적으로 대부분의 4.4부터 6.6의 안드로이드 커널을 지원합니다.
|
|
||||||
- GKI 2.0 (5.10+) 커널은 미리 빌드된 이미지와 LKM/KMI를 지원합니다.
|
|
||||||
- GKI 1.0 (4.19 - 5.4) 커널은 KernelSU 드라이버로 다시 빌드해야 합니다.
|
|
||||||
- EOL (<4.14) 커널도 역시 KernelSU 드라이버로 다시 빌드해야 합니다.(3.18+는 실험적이며 일부 함수의 이식이 필요할 수 있습니다.).
|
|
||||||
|
|
||||||
현재는, `arm64-v8a`, `armeabi-v7a` & `x86_64` 만 지원됩니다.
|
|
||||||
|
|
||||||
## 사용 방법
|
|
||||||
|
|
||||||
- [설치 방법](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## 보안
|
|
||||||
|
|
||||||
KernelSU의 보안 취약점 보고에 대한 자세한 내용은 [SECURITY.md](/SECURITY.md)를 참조하세요.
|
|
||||||
|
|
||||||
## 저작권 라이센스
|
|
||||||
|
|
||||||
- `kernel` 디렉터리의 파일은 [GPL-2.0전용](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)입니다.
|
|
||||||
- `kernel` 디렉터리를 제외한 모든 파일은 [GPL-3.0-이상](https://www.gnu.org/licenses/gpl-3.0.html)입니다.
|
|
||||||
|
|
||||||
## 크레딧
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU의 아이디어
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): 강력한 루팅 도구
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): APK v2 서명 검사
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): 일부 rootkit 기술
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): KernelSU Next가 존재할 수 있게 해 준 tiann에게 감사드립니다.
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): KernelSU를 구해준 5ec1cff에게 감사드립니다!
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Українська](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | **Polski** | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
|
||||||
|
|
||||||
<h2>KernelSU Next</h2>
|
|
||||||
<p><strong>Bazujące na jądrze rozwiązanie root dla urządzeń z Androidem.</strong></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
|
||||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
|
||||||
</a>
|
|
||||||
<a href="/LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Funkcjonalności
|
|
||||||
|
|
||||||
- Oparte na jądrze `su` i zarządzanie dostępem do roota.
|
|
||||||
- System modułów oparty na [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) i [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
- [Profil aplikacji](https://kernelsu.org/guide/app-profile.html): Ograniczaj uprawnienia roota dla poszczególnych aplikacji.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Kompatybilność
|
|
||||||
|
|
||||||
KernelSU Next obsługuje jądra Androida od wersji **4.4 do 6.6**:
|
|
||||||
|
|
||||||
| Wersja jądra | Informacje techniczne |
|
|
||||||
|----------------------|-------------------------------------------------------------------------------------------|
|
|
||||||
| 5.10+ (GKI 2.0) | Obsługuje wstępnie skompilowane obrazy i LKM/KMI |
|
|
||||||
| 4.19 – 5.4 (GKI 1.0) | Wymaga wbudowania sterownika KernelSU |
|
|
||||||
| < 4.14 (EOL) | Wymaga sterownika KernelSU (obsługa 3.18+ jest eksperymentalna i może wymagać backportów) |
|
|
||||||
|
|
||||||
**Obsługiwane architektury:** `arm64-v8a`, `armeabi-v7a` i `x86_64`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Instalacja
|
|
||||||
|
|
||||||
Instrukcje dotyczące instalacji można znaleźć w przewodniku [Instalacja](https://kernelsu-next.github.io/webpage/pages/installation.html).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Bezpieczeństwo
|
|
||||||
|
|
||||||
Aby zgłosić problemy związane z bezpieczeństwem, zapoznaj się z [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 Licencje
|
|
||||||
|
|
||||||
- **katalog `/kernel`:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- **Wszystkie pozostałe pliki:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💸 Darowizny
|
|
||||||
|
|
||||||
Jeśli chciałbyś wesprzeć projekt:
|
|
||||||
|
|
||||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
|
||||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
|
||||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 Podziękowania
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Inspiracja konceptem
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Bazowa implementacja roota
|
|
||||||
- [Genuine](https://github.com/brevent/genuine/) – Walidacja podpisu APK v2
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Techniki rootkit
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU) – Oryginalna baza, która umożliwiła powstanie KernelSU Next
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 dla 5ec1cff za utrzymanie KernelSU przy życiu
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | **Português (Brasil)** | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
|
||||||
|
|
||||||
<h2>KernelSU Next</h2>
|
|
||||||
<p><strong>Uma solução root baseada em kernel para dispositivos Android.</strong></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
|
||||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
|
||||||
</a>
|
|
||||||
<a href="/LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Características
|
|
||||||
|
|
||||||
- `su` e gerenciamento de acesso root baseado em kernel.
|
|
||||||
- Sistema de módulos baseado em [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) e [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
- [Perfil do app](https://kernelsu.org/pt_BR/guide/app-profile.html): Limitar privilégios root por app.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Compatibilidade
|
|
||||||
|
|
||||||
O KernelSU Next oferece suporte a kernels Android **4.4 até 6.6**:
|
|
||||||
|
|
||||||
| Versão do kernel | Notas de suporte |
|
|
||||||
|----------------------|-------------------------------------------------------------------------------|
|
|
||||||
| 5.10+ (GKI 2.0) | Suporta imagens pré-compiladas e LKM/KMI |
|
|
||||||
| 4.19 – 5.4 (GKI 1.0) | Requer driver do KernelSU integrado |
|
|
||||||
| < 4.14 (EOL) | Requer driver do KernelSU (3.18+ é experimental e pode precisar de backports) |
|
|
||||||
|
|
||||||
**Arquiteturas suportadas:** `arm64-v8a`, `armeabi-v7a` e `x86_64`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Instalação
|
|
||||||
|
|
||||||
Consulte o guia de [Instalação](https://kernelsu-next.github.io/webpage/pt_BR/pages/installation.html) para obter instruções de configuração.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Segurança
|
|
||||||
|
|
||||||
Para relatar problemas de segurança, consulte [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 Licença
|
|
||||||
|
|
||||||
- **Diretório `/kernel`:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- **Todos os outros arquivos:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💸 Doações
|
|
||||||
|
|
||||||
Se você quiser apoiar o projeto:
|
|
||||||
|
|
||||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
|
||||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
|
||||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 Créditos
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Inspiração do conceito
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Implementação root principal
|
|
||||||
- [Genuine](https://github.com/brevent/genuine/) – Validação de assinatura APK v2
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Técnicas de rootkit
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU) – A base original que tornou o KernelSU Next possível
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 para 5ec1cff por manter o KernelSU vivo
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | **Русский** | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
|
||||||
|
|
||||||
Root-решение для Android на базе ядра.
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## Функции
|
|
||||||
|
|
||||||
1. Реализация `su` и управление root-доступом прямо на уровне ядра.
|
|
||||||
2. Динамическая система модулей, построенная на [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
3. [Профиль для приложений](https://kernelsu.org/guide/app-profile.html): позволяет ограничить root-доступ в песочницу для отдельных приложений.
|
|
||||||
|
|
||||||
## Совместимость
|
|
||||||
|
|
||||||
KernelSU Next работает с большинством ядер Android (4.4 - 6.6):
|
|
||||||
- GKI 2.0 (5.10+) могут использовать предсобранные образы и LKM/KMI.
|
|
||||||
- GKI 1.0 (4.19 - 5.4) требуют пересборки с драйвером KernelSU.
|
|
||||||
- EOL (<4.14) также требуют пересборки с драйвером KernelSU (версии 3.18+ экспериментальные и могут потребовать некоторые функции бэкпортов).
|
|
||||||
|
|
||||||
Сейчас поддерживается только `arm64-v8a`, `armeabi-v7a` & `x86_64`.
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
- [Инструкция по установке](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## Безопасность
|
|
||||||
|
|
||||||
Если нашли баг, посмотри [SECURITY.md](/SECURITY.md) — там гайд, как сообщить о проблеме.
|
|
||||||
|
|
||||||
## Лицензия
|
|
||||||
|
|
||||||
- Всё, что в директории `kernel`, — [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- Остальной код, кроме директории `kernel`, под [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
## Донаты
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
|
||||||
|
|
||||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
|
||||||
|
|
||||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
|
||||||
|
|
||||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
|
||||||
|
|
||||||
## Благодарность
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Идея KernelSU.
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): Топовый инструмент для root.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): Валидация подписи APK v2.
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Некоторые навыки rootkit.
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): Спасибо tiann, без него KernelSU Next даже не существовал бы.
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff за сохранение KernelSU!
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | **ภาษาไทย** | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
|
||||||
|
|
||||||
โซลูชั่นรูทบนพื้นฐานเคอร์เนลสำหรับอุปกรณ์ Android
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## คุณสมบัติ
|
|
||||||
|
|
||||||
1. จัดการการเข้าถึงรูท และ `su` บนพื้นฐานเคอร์เนล
|
|
||||||
2. ระบบโมดูลแบบไดนามิกเมานต์ [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)
|
|
||||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): จำกัดสิทธิ์เข้าถึงรูทสำหรับแอปต่างๆ
|
|
||||||
|
|
||||||
## การเข้ากันในอุปกรณ์ต่างๆ
|
|
||||||
|
|
||||||
KernelSU Next รองรับแบบเป็นทางการตั้งแต่เคอร์เนลแอนดรอยด์ 4.4 ถึง 6.6
|
|
||||||
- GKI 2.0 (5.10+) เคอร์เนลสามารถรันไฟล์อิมเมจสำเร็จรูป และ LKM/KMI ได้
|
|
||||||
- GKI 1.0 (4.19 - 5.4) เคอร์เนลจะต้องรีบิ้วร่วมกับไดร์เวอร์ของ KernelSU
|
|
||||||
- EOL (<4.14) เคอร์เนลก็ต้องรีบิ้วร่วมกับไดร์เวอร์ของ KernelSU เช่นกัน (3.18+ ยังเป็นเวอร์ชั่นทดลอง และยังต้องเขียนฟังก์ชั่นหลังบ้านเพิ่มเติม)
|
|
||||||
|
|
||||||
ในขณะนี้, มีแค่สถาปัตยกรรม `arm64-v8a`, `armeabi-v7a` & `x86_64` ที่รองรับเท่านั้น
|
|
||||||
|
|
||||||
## การใช้งาน
|
|
||||||
|
|
||||||
- [คำแนะนำในการติดตั้ง](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## ความปลอดภัย
|
|
||||||
|
|
||||||
สำหรับข้อมูลการรายงานช่องโหว่ด้านความปลอดภัยของ KernelSU โปรดดูที่ [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
## สัญญาอนุญาต
|
|
||||||
|
|
||||||
- ไฟล์ภายใต้โฟลเดอร์ `kernel` ถือว่าเป็นสัญญาอนุญาต [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- ไฟล์ที่นอกเหนือจากโฟลเดอร์ `kernel` ถือว่าเป็นสัญญาอนุญาต [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
## การบริจาก
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
|
||||||
|
|
||||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
|
||||||
|
|
||||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
|
||||||
|
|
||||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
|
||||||
|
|
||||||
## เครดิต
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): ที่เป็นคนริเริ่มไอเดีย KernelSU
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): เครื่องมือรูทที่ทรงพลัง
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): การตรวจสอบลายเซ็น APK v2
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): ความรู้เกี่ยวกับ rootkit
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): ต้องขอบคุณ tiann ไม่งั้นจะไม่มี KernelSU ขึ้นมา
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 💜 5ec1cff ที่ช่วย KernelSU เอาไว้!
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | **Türkçe** | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Українська](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logosu">
|
|
||||||
|
|
||||||
<h2>KernelSU Next</h2>
|
|
||||||
<p><strong>Android cihazlar için çekirdek tabanlı bir root çözümüdür.</strong></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
|
||||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
|
||||||
</a>
|
|
||||||
<a href="/LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Özellikler
|
|
||||||
|
|
||||||
- Çekirdek tabanlı `su` ve root erişim yönetimi.
|
|
||||||
- **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** ve **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)** tabanlı modül sistemi.
|
|
||||||
- [Uygulama Profili](https://kernelsu.org/guide/app-profile.html): Uygulama başına root yetkisini sınırlandırma.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Uyumluluk
|
|
||||||
|
|
||||||
KernelSU Next, **4.4 ile 6.6** arasındaki Android çekirdeklerini destekler:
|
|
||||||
|
|
||||||
| Çekirdek Sürümü | Destek Notları |
|
|
||||||
|------------------------|--------------------------------------------------------------------------|
|
|
||||||
| 5.10+ (GKI 2.0) | Hazır imajlar ve LKM/KMI desteği |
|
|
||||||
| 4.19 – 5.4 (GKI 1.0) | KernelSU sürücüsünün çekirdeğe gömülü olması gerekir |
|
|
||||||
| < 4.14 (EOL) | KernelSU sürücüsü gerekir (3.18+ deneysel olup yama gerektirebilir) |
|
|
||||||
|
|
||||||
**Desteklenen mimariler:** `arm64-v8a`, `armeabi-v7a`, `x86_64`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Kurulum
|
|
||||||
|
|
||||||
Kurulum talimatları için [Kurulum Kılavuzu](https://kernelsu-next.github.io/webpage/pages/installation.html) sayfasına bakınız.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Güvenlik
|
|
||||||
|
|
||||||
Güvenlik açıklarını bildirmek için lütfen [SECURITY.md](/SECURITY.md) dosyasına bakınız.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 Lisans
|
|
||||||
|
|
||||||
- **`/kernel` dizini:** [Yalnızca GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
- **Diğer tüm dosyalar:** [GPL-3.0-veya-sonrası](https://www.gnu.org/licenses/gpl-3.0.html)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💸 Bağışlar
|
|
||||||
|
|
||||||
Projeye destek olmak isterseniz:
|
|
||||||
|
|
||||||
- **USDT (BEP20, ERC20):** `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **USDT (TRC20):** `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
|
||||||
- **ETH (ERC20):** `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **LTC:** `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
|
||||||
- **BTC:** `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 Katkıda Bulunanlar
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – KernelSU Fikrinin temeli
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Temel root altyapısı
|
|
||||||
- [Genuine](https://github.com/brevent/genuine/) – APK v2 imza doğrulaması
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Rootkit teknikleri
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU) – KernelSU Next'in temelini oluşturan orijinal proje
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – KernelSU’yu kurtardığı için 💜 5ec1cff’e teşekkürler
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | **繁體中文** | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
|
||||||
|
|
||||||
<h2>KernelSU Next</h2>
|
|
||||||
<p><strong>基於內核的 Android 設備 Root 解決方案</strong></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
|
||||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
|
||||||
</a>
|
|
||||||
<a href="/LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 特性
|
|
||||||
|
|
||||||
- 基於內核的 `su` 和 Root 權限管理
|
|
||||||
- 模塊系統基於 **[Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount)** 以及 **[OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)**
|
|
||||||
- [App Profile](https://kernelsu.org/zh_CN/guide/app-profile.html):把 Root 權限關進籠子裡
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 兼容狀態
|
|
||||||
|
|
||||||
KernelSU Next 正式支持大多數從 **4.4 到 6.6** 的 Android 內核
|
|
||||||
|
|
||||||
| 内核版本 | 支援狀況 |
|
|
||||||
|----------------|---------------|
|
|
||||||
| 5.10+ (GKI 2.0) | 可以運行預構建的映像和 LKM/KMI |
|
|
||||||
| 4.19 – 5.4 (GKI 1.0) | 需要重新編譯 KernelSU 驅動程序 |
|
|
||||||
| <4.14 (EOL) | 需要重新編譯 KernelSU 驅動程序(3.18+ 是實驗性的,可能需要回溯移植一些功能) |
|
|
||||||
|
|
||||||
**支援的架構:**
|
|
||||||
`arm64-v8a`、`armeabi-v7a`、`x86_64`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 用法
|
|
||||||
|
|
||||||
請遵循[安裝説明](https://kernelsu-next.github.io/webpage/pages/installation.html)進行操作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 安全性
|
|
||||||
|
|
||||||
有關報告 KernelSU Next 漏洞的信息,請參閱 [SECURITY.md](/SECURITY.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 許可證
|
|
||||||
|
|
||||||
- **目錄 `/kernel` 下所有文件**為 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
- **`/kernel` 目錄以外的其他部分**均為 [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💸 抖内
|
|
||||||
|
|
||||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
|
||||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
|
||||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 鳴謝
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/):KernelSU 的靈感.
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk):強大的 Root 工具.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/):APK v2 簽名驗證。
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 Rootkit 技巧。
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU):感謝 tiann,否則 KernelSU Next 根本不會存在。
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs):💜 5ec1cff 為了拯救 KernelSU!
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
**Languages**:
|
|
||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | **Українська** | [ภาษาไทย](README_TH.md) | [Tiếng Việt](README_VI.md) | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<img src="/assets/kernelsu_next.png" width="96" alt="KernelSU Next Logo">
|
|
||||||
|
|
||||||
<h2>KernelSU Next</h2>
|
|
||||||
<p><strong>Рішення для root-прав на основі ядра для пристроїв Android.</strong></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/KernelSU-Next/KernelSU-Next/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/KernelSU-Next/KernelSU-Next?label=Release&logo=github" alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<a href="https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager">
|
|
||||||
<img src="https://img.shields.io/badge/Nightly%20Release-gray?logo=hackthebox&logoColor=fff" alt="Nightly Build">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
|
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v2-orange.svg?logo=gnu" alt="License: GPL v2">
|
|
||||||
</a>
|
|
||||||
<a href="/LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/KernelSU-Next/KernelSU-Next?logo=gnu" alt="GitHub License">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Особливості
|
|
||||||
|
|
||||||
- Керування `su` та root-доступом на основі ядра.
|
|
||||||
- Модульна система на основі [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) та [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
- [Профілі програм](https://kernelsu.org/guide/app-profile.html): Обмеження root-прав для кожної програми.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Сумісність
|
|
||||||
|
|
||||||
KernelSU Next підтримує ядра Android від **4.4 до 6.6**:
|
|
||||||
|
|
||||||
| Версія ядра | Примітки підтримки |
|
|
||||||
|----------------------|-------------------------------------------------------------------------------------------|
|
|
||||||
| 5.10+ (GKI 2.0) | Підтримує попередньо створені образи та LKM/KMI |
|
|
||||||
| 4.19 – 5.4 (GKI 1.0) | Потрібен вбудований драйвер KernelSU |
|
|
||||||
| <4.14 (EOL) | Потрібен драйвер KernelSU (версія 3.18+ є експериментальною, може знадобитися портування) |
|
|
||||||
|
|
||||||
**Підтримувані архітектури:** `arm64-v8a`, `armeabi-v7a`, `x86_64`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Встановлення
|
|
||||||
|
|
||||||
Будь ласка, зверніться до [Посібника з встановлення](https://kernelsu-next.github.io/webpage/pages/installation.html) для отримання інструкцій з налаштування.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Безпека
|
|
||||||
|
|
||||||
Щоб повідомити про проблеми безпеки, див [SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 Ліцензія
|
|
||||||
|
|
||||||
- **Каталог `/kernel`:** [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
- **Усі інші файли:** [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💸 Пожертви
|
|
||||||
|
|
||||||
Якщо ви хочете підтримати проєкт:
|
|
||||||
|
|
||||||
- **USDT (BEP20, ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **USDT (TRC20)**: `TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh`
|
|
||||||
- **ETH (ERC20)**: `0x12b5224b7aca0121c2f003240a901e1d064371c1`
|
|
||||||
- **LTC**: `Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL`
|
|
||||||
- **BTC**: `19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 Подяки
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/) – Натхнення для концепції
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk) – Топовий інструмент для root
|
|
||||||
- [Genuine](https://github.com/brevent/genuine/) – Перевірка підпису APK версії 2
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine) – Деякі навики RootKit
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU) – Основа для KernelSU Next
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs) – 💜 до 5ec1cff за збереження KernelSU
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
[English](README.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [Türkçe](README_TR.md) | [Português (Brasil)](README_PT-BR.md) | [한국어](README_KO.md) | [Français](README_FR.md) | [Bahasa Indonesia](README_ID.md) | [Русский](README_RU.md) | [Український](README_UA.md) | [ภาษาไทย](README_TH.md) | **Tiếng Việt** | [Italiano](README_IT.md) | [Polski](README_PL.md) | [Български](README_BG.md) | [日本語](README_JA.md)
|
|
||||||
|
|
||||||
# KernelSU Next
|
|
||||||
|
|
||||||
<img src="/assets/kernelsu_next.png" style="width: 96px;" alt="logo">
|
|
||||||
|
|
||||||
Một giải pháp root từ nhân linux dành cho các thiết bị chạy Android
|
|
||||||
|
|
||||||
[](https://github.com/KernelSU-Next/KernelSU-Next/releases/latest)
|
|
||||||
[](https://nightly.link/KernelSU-Next/KernelSU-Next/workflows/build-manager-ci/next/Manager)
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
||||||
[](/LICENSE)
|
|
||||||
|
|
||||||
## Tính năng
|
|
||||||
|
|
||||||
1. Quản lý quyền truy cập SU dựa trên kernel android.
|
|
||||||
2. Hệ thống mount module dựa trên 1 trong 2 cơ chế mount [Magic Mount](https://topjohnwu.github.io/Magisk/details.html#magic-mount) / [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
|
|
||||||
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Quản lý quyền truy cập root 1 cách chặt chẽ
|
|
||||||
|
|
||||||
## Danh sách tương thích
|
|
||||||
|
|
||||||
KernelSU Next hỗ trợ chính thức các kernel Android từ phiên bản 4.4 đến 6.6
|
|
||||||
- GKI 2.0 (5.10+) kernels có thể cài đặt qua những .img/.zip đã được build sẵn và LKM/KMI hoặc tự vá qua manager (nếu được)
|
|
||||||
- GKI 1.0 (4.19 - 5.4) kernels cần dược build lại với các nhân KernelSU Next
|
|
||||||
- EOL (<4.14) kernels cần dược build lại với các nhân KernelSU Next (các kernels 3.18+ đang dược thử nghiệm và có thể cần backports 1 vài thứ ).
|
|
||||||
|
|
||||||
Hiện tại kernelSU Next chỉ hỗ trợ những cpu có `arm64-v8a`, `armeabi-v7a` & `x86_64`
|
|
||||||
|
|
||||||
## Sử dụng
|
|
||||||
|
|
||||||
- [Hướng dẫn vá KernelSU Next vào Kernel của bạn (yêu cầu kernel source)](https://ksunext.org/pages/installation.html)
|
|
||||||
|
|
||||||
## Bảo mật
|
|
||||||
|
|
||||||
Để biết thêm thông tin về việc báo cáo lỗ hổng bảo mật trong KernelSU Next vui lòng đọc (Thông tin sẽ dược gửi về KernelSU)[SECURITY.md](/SECURITY.md).
|
|
||||||
|
|
||||||
## Gíây phép
|
|
||||||
|
|
||||||
- Những thư mục/tập tin trong `kernel` là giấy phép [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
|
|
||||||
- Những thư mục/tập tin ngoài `kernel` là giấy phép [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
|
|
||||||
|
|
||||||
## Quyên góp/Hỗ trợ
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT BEP20 ]
|
|
||||||
|
|
||||||
- TYUVMWGTcnR5svnDoX85DWHyqUAeyQcdjh [ USDT TRC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ USDT ERC20 ]
|
|
||||||
|
|
||||||
- 0x12b5224b7aca0121c2f003240a901e1d064371c1 [ ETH ERC20 ]
|
|
||||||
|
|
||||||
- Ld238uYBuRQdZB5YwdbkuU6ektBAAUByoL [ LTC ]
|
|
||||||
|
|
||||||
- 19QgifcjMjSr1wB2DJcea5cxitvWVcXMT6 [ BTC ]
|
|
||||||
|
|
||||||
## Lời cảm ơn tới...
|
|
||||||
|
|
||||||
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): Ý tưởng cho sự ra đời của KernelSU.
|
|
||||||
- [Magisk](https://github.com/topjohnwu/Magisk): Công cụ root mạnh mẽ, quen thuộc và tương thích cao cho các thiết bị chạy Android.
|
|
||||||
- [genuine](https://github.com/brevent/genuine/): Chữ kí apk v2.
|
|
||||||
- [Diamorphine](https://github.com/m0nad/Diamorphine): Một vài kỹ năng rootkit.
|
|
||||||
- [KernelSU](https://github.com/tiann/KernelSU): Nguồn gốc của KernelSU Next, thanks to tiann.
|
|
||||||
- [Magic Mount Port](https://github.com/5ec1cff/KernelSU/blob/main/userspace/ksud/src/magic_mount.rs): 5ec1cff - người đã cứu lấy KernelSU💜 !
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
# WebUI-Next API Documentation
|
|
||||||
|
|
||||||
This document provides examples of how to use the `WebUI-Next` JavaScript APIs exposed to a module WebUI. These APIs allow code to run in the WebUI to interact with the system, execute shell commands, manage packages, control UI elements, and more coming soon.
|
|
||||||
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
1. [exec(cmd)](#exec-cmd)
|
|
||||||
2. [exec(cmd, callbackFunc)](#exec-cmd-callbackfunc)
|
|
||||||
3. [exec(cmd, options, callbackFunc)](#exec-cmd-options-callbackfunc)
|
|
||||||
4. [spawn(command, args, options, callbackFunc)](#spawn-command-args-options-callbackfunc)
|
|
||||||
5. [toast(msg)](#toast-msg)
|
|
||||||
6. [fullScreen(enable)](#fullscreen-enable)
|
|
||||||
7. [moduleInfo()](#moduleinfo)
|
|
||||||
8. [listSystemPackages()](#listsystempackages)
|
|
||||||
9. [listUserPackages()](#listuserpackages)
|
|
||||||
10. [listAllPackages()](#listallpackages)
|
|
||||||
11. [getPackagesInfo(packageNamesJson)](#getpackagesinfo-packagenamesjson)
|
|
||||||
12. [cacheAllPackageIcons(size)](#cacheallpackageicons-size)
|
|
||||||
13. [getPackagesIcons(packageNamesJson, size)](#getpackagesicons-packagenamesjson-size)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## exec(cmd)
|
|
||||||
|
|
||||||
Executes a shell command synchronously and returns the output as a string.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `cmd` (String): The shell command to execute.
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
- `String`: The command output (stdout).
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const output = ksu.exec("ls /system");
|
|
||||||
console.log("Output:", output);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## exec(cmd, callbackFunc)
|
|
||||||
|
|
||||||
Executes a shell command asynchronously and invokes the provided callback function with the result.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `cmd` (String): The shell command to execute.
|
|
||||||
- `callbackFunc` (String): The name of the JavaScript callback function to invoke with the result.
|
|
||||||
|
|
||||||
### Callback Signature
|
|
||||||
```javascript
|
|
||||||
function callbackFunc(exitCode, stdout, stderr) {
|
|
||||||
// Handle result
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
function handleResult(exitCode, stdout, stderr) {
|
|
||||||
console.log("Exit Code:", exitCode);
|
|
||||||
console.log("Stdout:", stdout);
|
|
||||||
console.log("Stderr:", stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
ksu.exec("ls /system", "handleResult");
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## exec(cmd, options, callbackFunc)
|
|
||||||
|
|
||||||
Executes a shell command asynchronously with options (e.g., working directory, environment variables) and invokes the provided callback function with the result.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `cmd` (String): The shell command to execute.
|
|
||||||
- `options` (String): A JSON string specifying options like `cwd` (working directory) and `env` (environment variables).
|
|
||||||
- `callbackFunc` (String): The name of the JavaScript callback function to invoke with the result.
|
|
||||||
|
|
||||||
### Options Format
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
"cwd": "/path/to/working/directory",
|
|
||||||
"env": {
|
|
||||||
"KEY1": "VALUE1",
|
|
||||||
"KEY2": "VALUE2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Callback Signature
|
|
||||||
```javascript
|
|
||||||
function callbackFunc(exitCode, stdout, stderr) {
|
|
||||||
// Handle result
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const options = JSON.stringify({
|
|
||||||
cwd: "/system",
|
|
||||||
env: { PATH: "/system/bin" }
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleResult(exitCode, stdout, stderr) {
|
|
||||||
console.log("Exit Code:", exitCode);
|
|
||||||
console.log("Stdout:", stdout);
|
|
||||||
console.log("Stderr:", stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
ksu.exec("ls", JSON.stringify(options), "handleResult");
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## spawn(command, args, options, callbackFunc)
|
|
||||||
|
|
||||||
Spawns a shell command with arguments and streams output through events to a JavaScript object.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `command` (String): The shell command to execute.
|
|
||||||
- `args` (String): A JSON array of command arguments.
|
|
||||||
- `options` (String): A JSON string specifying options like `cwd` and `env` (optional).
|
|
||||||
- `callbackFunc` (String): The name of the JavaScript object to receive events (`stdout`, `stderr`, `exit`, `error`).
|
|
||||||
|
|
||||||
### Callback Object
|
|
||||||
The callback object should implement methods to handle events:
|
|
||||||
- `stdout.emit('data', data)`: Emits stdout data.
|
|
||||||
- `stderr.emit('data', data)`: Emits stderr data.
|
|
||||||
- `exit(code)`: Emits the exit code.
|
|
||||||
- `error(err)`: Emits an error object with `exitCode` and `message`.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const streamHandler = {
|
|
||||||
stdout: {
|
|
||||||
emit: (event, data) => {
|
|
||||||
if (event === "data") console.log("Stdout:", data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stderr: {
|
|
||||||
emit: (event, data) => {
|
|
||||||
if (event === "data") console.log("Stderr:", data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emit: (event, data) => {
|
|
||||||
if (event === "exit") console.log("Exit Code:", data);
|
|
||||||
if (event === "error") console.error("Error:", data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const args = JSON.stringify(["-l", "/system"]);
|
|
||||||
const options = JSON.stringify({ cwd: "/system" });
|
|
||||||
|
|
||||||
ksu.spawn("ls", args, options, "streamHandler");
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## toast(msg)
|
|
||||||
|
|
||||||
Displays a short Android toast message.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `msg` (String): The message to display.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
ksu.toast("Hello from WebUI-Next!");
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## fullScreen(enable)
|
|
||||||
|
|
||||||
Toggles full-screen mode by hiding or showing system UI (status and navigation bars).
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `enable` (Boolean): `true` to enable full-screen mode, `false` to disable.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
// Enable full-screen
|
|
||||||
ksu.fullScreen(true);
|
|
||||||
|
|
||||||
// Disable full-screen
|
|
||||||
ksu.fullScreen(false);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## moduleInfo()
|
|
||||||
|
|
||||||
Returns information about the current module as a JSON string.
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
- `String`: A JSON string containing module information, including `moduleDir` and other module-specific details.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const moduleInfo = JSON.parse(ksu.moduleInfo());
|
|
||||||
console.log("Module Directory:", moduleInfo.moduleDir);
|
|
||||||
console.log("Module ID:", moduleInfo.id);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## listSystemPackages()
|
|
||||||
|
|
||||||
Returns a list of system package names as a JSON array.
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
- `String`: A JSON array of system package names.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const systemPackages = JSON.parse(ksu.listSystemPackages());
|
|
||||||
console.log("System Packages:", systemPackages);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## listUserPackages()
|
|
||||||
|
|
||||||
Returns a list of user-installed package names as a JSON array.
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
- `String`: A JSON array of user package names.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const userPackages = JSON.parse(ksu.listUserPackages());
|
|
||||||
console.log("User Packages:", userPackages);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## listAllPackages()
|
|
||||||
|
|
||||||
Returns a list of all installed package names as a JSON array.
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
- `String`: A JSON array of all package names.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const allPackages = JSON.parse(ksu.listAllPackages());
|
|
||||||
console.log("All Packages:", allPackages);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## getPackagesInfo(packageNamesJson)
|
|
||||||
|
|
||||||
Returns detailed information about specified packages as a JSON array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `packageNamesJson` (String): A JSON array of package names.
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
- `String`: A JSON array of objects containing package details (`packageName`, `versionName`, `versionCode`, `appLabel`, `isSystem`, `uid`) or an error object if the package is not found.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const packageNames = JSON.stringify(["com.android.settings", "com.example.app"]);
|
|
||||||
const packageInfos = JSON.parse(ksu.getPackagesInfo(packageNames));
|
|
||||||
packageInfos.forEach(info => {
|
|
||||||
if (info.error) {
|
|
||||||
console.error(`Error for ${info.packageName}: ${info.error}`);
|
|
||||||
} else {
|
|
||||||
console.log(`Package: ${info.packageName}, Version: ${info.versionName}, System: ${info.isSystem}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## cacheAllPackageIcons(size)
|
|
||||||
|
|
||||||
Caches icons for all installed packages at the specified size.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `size` (Number): The size (in pixels) for the square icon.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
// Cache all package icons at 48x48 pixels
|
|
||||||
ksu.cacheAllPackageIcons(48);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## getPackagesIcons(packageNamesJson, size)
|
|
||||||
|
|
||||||
Returns base64-encoded icons for specified packages as a JSON array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
- `packageNamesJson` (String): A JSON array of package names.
|
|
||||||
- `size` (Number): The size (in pixels) for the square icon.
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
- `String`: A JSON array of objects containing `packageName` and `icon` (base64-encoded PNG or empty string if unavailable).
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```javascript
|
|
||||||
const packageNames = JSON.stringify(["com.android.settings", "com.example.app"]);
|
|
||||||
const packageIcons = JSON.parse(ksu.getPackagesIcons(packageNames, 48));
|
|
||||||
packageIcons.forEach(item => {
|
|
||||||
if (item.icon) {
|
|
||||||
console.log(`Icon for ${item.packageName}: ${item.icon.substring(0, 30)}...`);
|
|
||||||
// Example: Display icon in an <img> element
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = item.icon;
|
|
||||||
document.body.appendChild(img);
|
|
||||||
} else {
|
|
||||||
console.log(`No icon for ${item.packageName}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
- **Root Access**: Methods like `exec` and `spawn` require root access and use the `libsu` library for shell execution.
|
|
||||||
- **Asynchronous Operations**: Use `WebUI.post` to ensure UI thread safety when invoking JavaScript callbacks.
|
|
||||||
- **Error Handling**: Always check for errors in callbacks (e.g., `stderr` in `exec`, `error` event in `spawn`).
|
|
||||||
- **Icon Caching**: Use `cacheAllPackageIcons` to improve performance for subsequent `getPackagesIcons` calls.
|
|
||||||
- **JSON Parsing**: Ensure valid JSON strings are passed to methods like `getPackagesInfo` and `getPackagesIcons`.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
alias bk := build_ksud
|
|
||||||
alias bm := build_manager
|
|
||||||
|
|
||||||
build_ksud:
|
|
||||||
cross build --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml
|
|
||||||
|
|
||||||
build_manager: build_ksud
|
|
||||||
cp userspace/ksud/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud.so
|
|
||||||
cd manager && ./gradlew aDebug
|
|
||||||
|
|
||||||
clippy:
|
|
||||||
cargo fmt --manifest-path ./userspace/ksud/Cargo.toml
|
|
||||||
cross clippy --target x86_64-pc-windows-gnu --release --manifest-path ./userspace/ksud/Cargo.toml
|
|
||||||
cross clippy --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
*.iml
|
|
||||||
.gradle
|
|
||||||
.idea
|
|
||||||
.kotlin
|
|
||||||
.DS_Store
|
|
||||||
build
|
|
||||||
captures
|
|
||||||
.cxx
|
|
||||||
local.properties
|
|
||||||
key.jks
|
|
||||||
setup.sh
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/build
|
|
||||||
/release/
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
@file:Suppress("UnstableApiUsage")
|
|
||||||
|
|
||||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
|
||||||
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
alias(libs.plugins.agp.app)
|
|
||||||
alias(libs.plugins.kotlin)
|
|
||||||
alias(libs.plugins.compose.compiler)
|
|
||||||
alias(libs.plugins.ksp)
|
|
||||||
alias(libs.plugins.lsplugin.apksign)
|
|
||||||
id("kotlin-parcelize")
|
|
||||||
}
|
|
||||||
|
|
||||||
val managerVersionCode: Int by rootProject.extra
|
|
||||||
val managerVersionName: String by rootProject.extra
|
|
||||||
|
|
||||||
apksign {
|
|
||||||
storeFileProperty = "KEYSTORE_FILE"
|
|
||||||
storePasswordProperty = "KEYSTORE_PASSWORD"
|
|
||||||
keyAliasProperty = "KEY_ALIAS"
|
|
||||||
keyPasswordProperty = "KEY_PASSWORD"
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "com.rifsxd.ksunext"
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
isMinifyEnabled = true
|
|
||||||
isShrinkResources = true
|
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
|
||||||
aidl = true
|
|
||||||
buildConfig = true
|
|
||||||
compose = true
|
|
||||||
prefab = true
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "21"
|
|
||||||
}
|
|
||||||
|
|
||||||
packaging {
|
|
||||||
jniLibs {
|
|
||||||
useLegacyPackaging = true
|
|
||||||
}
|
|
||||||
resources {
|
|
||||||
// https://stackoverflow.com/a/58956288
|
|
||||||
// It will break Layout Inspector, but it's unused for release build.
|
|
||||||
excludes += "META-INF/*.version"
|
|
||||||
// https://github.com/Kotlin/kotlinx.coroutines?tab=readme-ov-file#avoiding-including-the-debug-infrastructure-in-the-resulting-apk
|
|
||||||
excludes += "DebugProbesKt.bin"
|
|
||||||
// https://issueantenna.com/repo/kotlin/kotlinx.coroutines/issues/3158
|
|
||||||
excludes += "kotlin-tooling-metadata.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
path("src/main/cpp/CMakeLists.txt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationVariants.all {
|
|
||||||
outputs.forEach {
|
|
||||||
val output = it as BaseVariantOutputImpl
|
|
||||||
output.outputFileName = "KernelSU_Next_${managerVersionName}_${managerVersionCode}-$name.apk"
|
|
||||||
}
|
|
||||||
kotlin.sourceSets {
|
|
||||||
getByName(name) {
|
|
||||||
kotlin.srcDir("build/generated/ksp/$name/kotlin")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/77745844
|
|
||||||
tasks.withType<PackageAndroidArtifact> {
|
|
||||||
doFirst { appMetadata.asFile.orNull?.writeText("") }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependenciesInfo {
|
|
||||||
includeInApk = false
|
|
||||||
includeInBundle = false
|
|
||||||
}
|
|
||||||
|
|
||||||
androidResources {
|
|
||||||
generateLocaleConfig = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(libs.androidx.activity.compose)
|
|
||||||
implementation(libs.androidx.navigation.compose)
|
|
||||||
|
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
|
||||||
implementation(libs.androidx.compose.material.icons.extended)
|
|
||||||
implementation(libs.androidx.compose.material)
|
|
||||||
implementation(libs.androidx.compose.material3)
|
|
||||||
implementation(libs.androidx.compose.ui)
|
|
||||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
|
||||||
|
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
|
||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
|
||||||
|
|
||||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
|
||||||
|
|
||||||
implementation(libs.compose.destinations.core)
|
|
||||||
ksp(libs.compose.destinations.ksp)
|
|
||||||
|
|
||||||
implementation(libs.com.github.topjohnwu.libsu.core)
|
|
||||||
implementation(libs.com.github.topjohnwu.libsu.service)
|
|
||||||
implementation(libs.com.github.topjohnwu.libsu.io)
|
|
||||||
|
|
||||||
implementation(libs.dev.rikka.rikkax.parcelablelist)
|
|
||||||
|
|
||||||
implementation(libs.io.coil.kt.coil.compose)
|
|
||||||
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
|
|
||||||
implementation(libs.me.zhanghai.android.appiconloader.coil)
|
|
||||||
|
|
||||||
implementation(libs.sheet.compose.dialogs.core)
|
|
||||||
implementation(libs.sheet.compose.dialogs.list)
|
|
||||||
implementation(libs.sheet.compose.dialogs.input)
|
|
||||||
|
|
||||||
implementation(libs.markdown)
|
|
||||||
implementation(libs.androidx.webkit)
|
|
||||||
|
|
||||||
implementation(libs.lsposed.cxx)
|
|
||||||
|
|
||||||
implementation(libs.mmrl.ui)
|
|
||||||
}
|
|
||||||
Vendored
-39
@@ -1,39 +0,0 @@
|
|||||||
-verbose
|
|
||||||
-optimizationpasses 5
|
|
||||||
|
|
||||||
-dontwarn org.conscrypt.**
|
|
||||||
-dontwarn kotlinx.serialization.**
|
|
||||||
|
|
||||||
# Please add these rules to your existing keep rules in order to suppress warnings.
|
|
||||||
# This is generated automatically by the Android Gradle plugin.
|
|
||||||
-dontwarn com.google.auto.service.AutoService
|
|
||||||
-dontwarn com.google.j2objc.annotations.RetainedWith
|
|
||||||
-dontwarn javax.lang.model.SourceVersion
|
|
||||||
-dontwarn javax.lang.model.element.AnnotationMirror
|
|
||||||
-dontwarn javax.lang.model.element.AnnotationValue
|
|
||||||
-dontwarn javax.lang.model.element.Element
|
|
||||||
-dontwarn javax.lang.model.element.ElementKind
|
|
||||||
-dontwarn javax.lang.model.element.ElementVisitor
|
|
||||||
-dontwarn javax.lang.model.element.ExecutableElement
|
|
||||||
-dontwarn javax.lang.model.element.Modifier
|
|
||||||
-dontwarn javax.lang.model.element.Name
|
|
||||||
-dontwarn javax.lang.model.element.PackageElement
|
|
||||||
-dontwarn javax.lang.model.element.TypeElement
|
|
||||||
-dontwarn javax.lang.model.element.TypeParameterElement
|
|
||||||
-dontwarn javax.lang.model.element.VariableElement
|
|
||||||
-dontwarn javax.lang.model.type.ArrayType
|
|
||||||
-dontwarn javax.lang.model.type.DeclaredType
|
|
||||||
-dontwarn javax.lang.model.type.ExecutableType
|
|
||||||
-dontwarn javax.lang.model.type.TypeKind
|
|
||||||
-dontwarn javax.lang.model.type.TypeMirror
|
|
||||||
-dontwarn javax.lang.model.type.TypeVariable
|
|
||||||
-dontwarn javax.lang.model.type.TypeVisitor
|
|
||||||
-dontwarn javax.lang.model.util.AbstractAnnotationValueVisitor8
|
|
||||||
-dontwarn javax.lang.model.util.AbstractTypeVisitor8
|
|
||||||
-dontwarn javax.lang.model.util.ElementFilter
|
|
||||||
-dontwarn javax.lang.model.util.Elements
|
|
||||||
-dontwarn javax.lang.model.util.SimpleElementVisitor8
|
|
||||||
-dontwarn javax.lang.model.util.SimpleTypeVisitor7
|
|
||||||
-dontwarn javax.lang.model.util.SimpleTypeVisitor8
|
|
||||||
-dontwarn javax.lang.model.util.Types
|
|
||||||
-dontwarn javax.tools.Diagnostic$Kind
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:name=".KernelSUApplication"
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/Theme.KernelSU"
|
|
||||||
tools:targetApi="34">
|
|
||||||
<activity
|
|
||||||
android:name=".ui.MainActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/Theme.KernelSU">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:mimeType="application/zip" />
|
|
||||||
<data android:scheme="file" />
|
|
||||||
<data android:scheme="content" />
|
|
||||||
<data android:pathPattern=".*\\.zip" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.webui.WebUIActivity"
|
|
||||||
android:autoRemoveFromRecents="true"
|
|
||||||
android:documentLaunchMode="intoExisting"
|
|
||||||
android:exported="false"
|
|
||||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.webui.WebUIXActivity"
|
|
||||||
android:autoRemoveFromRecents="true"
|
|
||||||
android:documentLaunchMode="intoExisting"
|
|
||||||
android:exported="false"
|
|
||||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="${applicationId}.fileprovider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/filepaths" />
|
|
||||||
</provider>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// IKsuInterface.aidl
|
|
||||||
package com.rifsxd.ksunext;
|
|
||||||
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import rikka.parcelablelist.ParcelableListSlice;
|
|
||||||
|
|
||||||
interface IKsuInterface {
|
|
||||||
ParcelableListSlice<PackageInfo> getPackages(int flags);
|
|
||||||
}
|
|
||||||
-9
File diff suppressed because one or more lines are too long
@@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
# For more information about using CMake with Android Studio, read the
|
|
||||||
# documentation: https://d.android.com/studio/projects/add-native-code.html
|
|
||||||
|
|
||||||
# Sets the minimum version of CMake required to build the native library.
|
|
||||||
cmake_minimum_required(VERSION 3.18.1)
|
|
||||||
|
|
||||||
project("kernelsu")
|
|
||||||
|
|
||||||
find_package(cxx REQUIRED CONFIG)
|
|
||||||
link_libraries(cxx::cxx)
|
|
||||||
|
|
||||||
add_library(kernelsu
|
|
||||||
SHARED
|
|
||||||
jni.cc
|
|
||||||
ksu.cc
|
|
||||||
)
|
|
||||||
|
|
||||||
find_library(log-lib log)
|
|
||||||
|
|
||||||
target_link_libraries(kernelsu ${log-lib})
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
|
|
||||||
#include <android/log.h>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "ksu.h"
|
|
||||||
|
|
||||||
#define LOG_TAG "KernelSU-Next"
|
|
||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
|
|
||||||
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
|
|
||||||
auto result = become_manager(cpkg);
|
|
||||||
env->ReleaseStringUTFChars(pkg, cpkg);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_getVersion(JNIEnv *env, jobject) {
|
|
||||||
return get_version();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_getManagerUid(JNIEnv *env, jobject) {
|
|
||||||
uid_t manager_uid = get_manager_uid();
|
|
||||||
return (jint)manager_uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jstring JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_getHookMode(JNIEnv *env, jobject) {
|
|
||||||
const char* mode = get_hook_mode();
|
|
||||||
return env->NewStringUTF(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jintArray JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_getAllowList(JNIEnv *env, jobject) {
|
|
||||||
int uids[1024];
|
|
||||||
int size = 0;
|
|
||||||
bool result = get_allow_list(uids, &size);
|
|
||||||
LOGD("getAllowList: %d, size: %d", result, size);
|
|
||||||
if (result) {
|
|
||||||
auto array = env->NewIntArray(size);
|
|
||||||
env->SetIntArrayRegion(array, 0, size, uids);
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
return env->NewIntArray(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
|
|
||||||
return is_safe_mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
|
|
||||||
return is_lkm_mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) {
|
|
||||||
auto cls = env->GetObjectClass(list);
|
|
||||||
auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z");
|
|
||||||
auto integerCls = env->FindClass("java/lang/Integer");
|
|
||||||
auto constructor = env->GetMethodID(integerCls, "<init>", "(I)V");
|
|
||||||
for (int i = 0; i < count; ++i) {
|
|
||||||
auto integer = env->NewObject(integerCls, constructor, data[i]);
|
|
||||||
env->CallBooleanMethod(list, add, integer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addIntToList(JNIEnv *env, jobject list, int ele) {
|
|
||||||
auto cls = env->GetObjectClass(list);
|
|
||||||
auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z");
|
|
||||||
auto integerCls = env->FindClass("java/lang/Integer");
|
|
||||||
auto constructor = env->GetMethodID(integerCls, "<init>", "(I)V");
|
|
||||||
auto integer = env->NewObject(integerCls, constructor, ele);
|
|
||||||
env->CallBooleanMethod(list, add, integer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t capListToBits(JNIEnv *env, jobject list) {
|
|
||||||
auto cls = env->GetObjectClass(list);
|
|
||||||
auto get = env->GetMethodID(cls, "get", "(I)Ljava/lang/Object;");
|
|
||||||
auto size = env->GetMethodID(cls, "size", "()I");
|
|
||||||
auto listSize = env->CallIntMethod(list, size);
|
|
||||||
auto integerCls = env->FindClass("java/lang/Integer");
|
|
||||||
auto intValue = env->GetMethodID(integerCls, "intValue", "()I");
|
|
||||||
uint64_t result = 0;
|
|
||||||
for (int i = 0; i < listSize; ++i) {
|
|
||||||
auto integer = env->CallObjectMethod(list, get, i);
|
|
||||||
int data = env->CallIntMethod(integer, intValue);
|
|
||||||
|
|
||||||
if (cap_valid(data)) {
|
|
||||||
result |= (1ULL << data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int getListSize(JNIEnv *env, jobject list) {
|
|
||||||
auto cls = env->GetObjectClass(list);
|
|
||||||
auto size = env->GetMethodID(cls, "size", "()I");
|
|
||||||
return env->CallIntMethod(list, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
|
|
||||||
auto cls = env->GetObjectClass(list);
|
|
||||||
auto get = env->GetMethodID(cls, "get", "(I)Ljava/lang/Object;");
|
|
||||||
auto integerCls = env->FindClass("java/lang/Integer");
|
|
||||||
auto intValue = env->GetMethodID(integerCls, "intValue", "()I");
|
|
||||||
for (int i = 0; i < count; ++i) {
|
|
||||||
auto integer = env->CallObjectMethod(list, get, i);
|
|
||||||
data[i] = env->CallIntMethod(integer, intValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jobject JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
|
|
||||||
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
p_key_t key = {};
|
|
||||||
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
|
|
||||||
strcpy(key, cpkg);
|
|
||||||
env->ReleaseStringUTFChars(pkg, cpkg);
|
|
||||||
|
|
||||||
app_profile profile = {};
|
|
||||||
profile.version = KSU_APP_PROFILE_VER;
|
|
||||||
|
|
||||||
strcpy(profile.key, key);
|
|
||||||
profile.current_uid = uid;
|
|
||||||
|
|
||||||
bool useDefaultProfile = !get_app_profile(key, &profile);
|
|
||||||
|
|
||||||
auto cls = env->FindClass("com/rifsxd/ksunext/Natives$Profile");
|
|
||||||
auto constructor = env->GetMethodID(cls, "<init>", "()V");
|
|
||||||
auto obj = env->NewObject(cls, constructor);
|
|
||||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
|
||||||
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
|
|
||||||
auto allowSuField = env->GetFieldID(cls, "allowSu", "Z");
|
|
||||||
|
|
||||||
auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z");
|
|
||||||
auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;");
|
|
||||||
|
|
||||||
auto uidField = env->GetFieldID(cls, "uid", "I");
|
|
||||||
auto gidField = env->GetFieldID(cls, "gid", "I");
|
|
||||||
auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;");
|
|
||||||
auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;");
|
|
||||||
auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;");
|
|
||||||
auto namespacesField = env->GetFieldID(cls, "namespace", "I");
|
|
||||||
|
|
||||||
auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z");
|
|
||||||
auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z");
|
|
||||||
|
|
||||||
env->SetObjectField(obj, keyField, env->NewStringUTF(profile.key));
|
|
||||||
env->SetIntField(obj, currentUidField, profile.current_uid);
|
|
||||||
|
|
||||||
if (useDefaultProfile) {
|
|
||||||
// no profile found, so just use default profile:
|
|
||||||
// don't allow root and use default profile!
|
|
||||||
LOGD("use default profile for: %s, %d", key, uid);
|
|
||||||
|
|
||||||
// allow_su = false
|
|
||||||
// non root use default = true
|
|
||||||
env->SetBooleanField(obj, allowSuField, false);
|
|
||||||
env->SetBooleanField(obj, nonRootUseDefaultField, true);
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto allowSu = profile.allow_su;
|
|
||||||
|
|
||||||
if (allowSu) {
|
|
||||||
env->SetBooleanField(obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
|
|
||||||
if (strlen(profile.rp_config.template_name) > 0) {
|
|
||||||
env->SetObjectField(obj, rootTemplateField,
|
|
||||||
env->NewStringUTF(profile.rp_config.template_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
env->SetIntField(obj, uidField, profile.rp_config.profile.uid);
|
|
||||||
env->SetIntField(obj, gidField, profile.rp_config.profile.gid);
|
|
||||||
|
|
||||||
jobject groupList = env->GetObjectField(obj, groupsField);
|
|
||||||
int groupCount = profile.rp_config.profile.groups_count;
|
|
||||||
if (groupCount > KSU_MAX_GROUPS) {
|
|
||||||
LOGD("kernel group count too large: %d???", groupCount);
|
|
||||||
groupCount = KSU_MAX_GROUPS;
|
|
||||||
}
|
|
||||||
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
|
|
||||||
|
|
||||||
jobject capList = env->GetObjectField(obj, capabilitiesField);
|
|
||||||
for (int i = 0; i <= CAP_LAST_CAP; i++) {
|
|
||||||
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
|
|
||||||
addIntToList(env, capList, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
env->SetObjectField(obj, domainField,
|
|
||||||
env->NewStringUTF(profile.rp_config.profile.selinux_domain));
|
|
||||||
env->SetIntField(obj, namespacesField, profile.rp_config.profile.namespaces);
|
|
||||||
env->SetBooleanField(obj, allowSuField, profile.allow_su);
|
|
||||||
} else {
|
|
||||||
env->SetBooleanField(obj, nonRootUseDefaultField,
|
|
||||||
(jboolean) profile.nrp_config.use_default);
|
|
||||||
env->SetBooleanField(obj, umountModulesField, profile.nrp_config.profile.umount_modules);
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
|
|
||||||
auto cls = env->FindClass("com/rifsxd/ksunext/Natives$Profile");
|
|
||||||
|
|
||||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
|
||||||
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
|
|
||||||
auto allowSuField = env->GetFieldID(cls, "allowSu", "Z");
|
|
||||||
|
|
||||||
auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z");
|
|
||||||
auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;");
|
|
||||||
|
|
||||||
auto uidField = env->GetFieldID(cls, "uid", "I");
|
|
||||||
auto gidField = env->GetFieldID(cls, "gid", "I");
|
|
||||||
auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;");
|
|
||||||
auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;");
|
|
||||||
auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;");
|
|
||||||
auto namespacesField = env->GetFieldID(cls, "namespace", "I");
|
|
||||||
|
|
||||||
auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z");
|
|
||||||
auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z");
|
|
||||||
|
|
||||||
auto key = env->GetObjectField(profile, keyField);
|
|
||||||
if (!key) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (env->GetStringLength((jstring) key) > KSU_MAX_PACKAGE_NAME) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpkg = env->GetStringUTFChars((jstring) key, nullptr);
|
|
||||||
p_key_t p_key = {};
|
|
||||||
strcpy(p_key, cpkg);
|
|
||||||
env->ReleaseStringUTFChars((jstring) key, cpkg);
|
|
||||||
|
|
||||||
auto currentUid = env->GetIntField(profile, currentUidField);
|
|
||||||
|
|
||||||
auto uid = env->GetIntField(profile, uidField);
|
|
||||||
auto gid = env->GetIntField(profile, gidField);
|
|
||||||
auto groups = env->GetObjectField(profile, groupsField);
|
|
||||||
auto capabilities = env->GetObjectField(profile, capabilitiesField);
|
|
||||||
auto domain = env->GetObjectField(profile, domainField);
|
|
||||||
auto allowSu = env->GetBooleanField(profile, allowSuField);
|
|
||||||
auto umountModules = env->GetBooleanField(profile, umountModulesField);
|
|
||||||
|
|
||||||
app_profile p = {};
|
|
||||||
p.version = KSU_APP_PROFILE_VER;
|
|
||||||
|
|
||||||
strcpy(p.key, p_key);
|
|
||||||
p.allow_su = allowSu;
|
|
||||||
p.current_uid = currentUid;
|
|
||||||
|
|
||||||
if (allowSu) {
|
|
||||||
p.rp_config.use_default = env->GetBooleanField(profile, rootUseDefaultField);
|
|
||||||
auto templateName = env->GetObjectField(profile, rootTemplateField);
|
|
||||||
if (templateName) {
|
|
||||||
auto ctemplateName = env->GetStringUTFChars((jstring) templateName, nullptr);
|
|
||||||
strcpy(p.rp_config.template_name, ctemplateName);
|
|
||||||
env->ReleaseStringUTFChars((jstring) templateName, ctemplateName);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.rp_config.profile.uid = uid;
|
|
||||||
p.rp_config.profile.gid = gid;
|
|
||||||
|
|
||||||
int groups_count = getListSize(env, groups);
|
|
||||||
if (groups_count > KSU_MAX_GROUPS) {
|
|
||||||
LOGD("groups count too large: %d", groups_count);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
p.rp_config.profile.groups_count = groups_count;
|
|
||||||
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
|
|
||||||
|
|
||||||
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
|
|
||||||
|
|
||||||
auto cdomain = env->GetStringUTFChars((jstring) domain, nullptr);
|
|
||||||
strcpy(p.rp_config.profile.selinux_domain, cdomain);
|
|
||||||
env->ReleaseStringUTFChars((jstring) domain, cdomain);
|
|
||||||
|
|
||||||
p.rp_config.profile.namespaces = env->GetIntField(profile, namespacesField);
|
|
||||||
} else {
|
|
||||||
p.nrp_config.use_default = env->GetBooleanField(profile, nonRootUseDefaultField);
|
|
||||||
p.nrp_config.profile.umount_modules = umountModules;
|
|
||||||
}
|
|
||||||
|
|
||||||
return set_app_profile(&p);
|
|
||||||
}
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
|
|
||||||
return uid_should_umount(uid);
|
|
||||||
}
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
|
|
||||||
return is_su_enabled();
|
|
||||||
}
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
|
|
||||||
return set_su_enabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_rifsxd_ksunext_Natives_isZygiskEnabled(JNIEnv *env, jobject) {
|
|
||||||
return is_zygisk_enabled();
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by weishu on 2022/12/9.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "ksu.h"
|
|
||||||
|
|
||||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
|
||||||
|
|
||||||
#define CMD_GRANT_ROOT 0
|
|
||||||
|
|
||||||
#define CMD_BECOME_MANAGER 1
|
|
||||||
#define CMD_GET_VERSION 2
|
|
||||||
#define CMD_ALLOW_SU 3
|
|
||||||
#define CMD_DENY_SU 4
|
|
||||||
#define CMD_GET_SU_LIST 5
|
|
||||||
#define CMD_GET_DENY_LIST 6
|
|
||||||
#define CMD_CHECK_SAFEMODE 9
|
|
||||||
|
|
||||||
#define CMD_GET_APP_PROFILE 10
|
|
||||||
#define CMD_SET_APP_PROFILE 11
|
|
||||||
|
|
||||||
#define CMD_IS_UID_GRANTED_ROOT 12
|
|
||||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
|
||||||
#define CMD_IS_SU_ENABLED 14
|
|
||||||
#define CMD_ENABLE_SU 15
|
|
||||||
#define CMD_GET_MANAGER_UID 16
|
|
||||||
|
|
||||||
#define CMD_HOOK_MODE 0xC0DEAD1A
|
|
||||||
|
|
||||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
|
||||||
int32_t result = 0;
|
|
||||||
prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
|
|
||||||
return result == KERNEL_SU_OPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool become_manager(const char* pkg) {
|
|
||||||
char param[128];
|
|
||||||
uid_t uid = getuid();
|
|
||||||
uint32_t userId = uid / 100000;
|
|
||||||
if (userId == 0) {
|
|
||||||
sprintf(param, "/data/data/%s", pkg);
|
|
||||||
} else {
|
|
||||||
snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ksuctl(CMD_BECOME_MANAGER, param, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// cache the result to avoid unnecessary syscall
|
|
||||||
static bool is_lkm = false;
|
|
||||||
|
|
||||||
int get_version(void) {
|
|
||||||
int32_t version = -1;
|
|
||||||
int32_t flags = 0;
|
|
||||||
ksuctl(CMD_GET_VERSION, &version, &flags);
|
|
||||||
if (!is_lkm && (flags & 0x1)) {
|
|
||||||
is_lkm = true;
|
|
||||||
}
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
uid_t get_manager_uid() {
|
|
||||||
uid_t manager_uid = 0;
|
|
||||||
ksuctl(CMD_GET_MANAGER_UID, &manager_uid, nullptr);
|
|
||||||
return manager_uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* get_hook_mode() {
|
|
||||||
static char mode[16];
|
|
||||||
ksuctl(CMD_HOOK_MODE, mode, nullptr);
|
|
||||||
return mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool get_allow_list(int *uids, int *size) {
|
|
||||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_safe_mode() {
|
|
||||||
return ksuctl(CMD_CHECK_SAFEMODE, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_lkm_mode() {
|
|
||||||
// you should call get_version first!
|
|
||||||
return is_lkm;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool uid_should_umount(int uid) {
|
|
||||||
bool should;
|
|
||||||
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, reinterpret_cast<void*>(uid), &should) && should;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_app_profile(const app_profile *profile) {
|
|
||||||
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_app_profile(p_key_t key, app_profile *profile) {
|
|
||||||
return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_su_enabled(bool enabled) {
|
|
||||||
return ksuctl(CMD_ENABLE_SU, (void*) enabled, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_su_enabled() {
|
|
||||||
bool enabled = true;
|
|
||||||
// if ksuctl failed, we assume su is enabled, and it cannot be disabled.
|
|
||||||
ksuctl(CMD_IS_SU_ENABLED, &enabled, nullptr);
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_zygisk_enabled() {
|
|
||||||
return !!getenv("ZYGISK_ENABLED");
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by weishu on 2022/12/9.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef KERNELSU_KSU_H
|
|
||||||
#define KERNELSU_KSU_H
|
|
||||||
|
|
||||||
#include <linux/capability.h>
|
|
||||||
|
|
||||||
bool become_manager(const char *);
|
|
||||||
|
|
||||||
int get_version();
|
|
||||||
|
|
||||||
uid_t get_manager_uid();
|
|
||||||
|
|
||||||
const char* get_hook_mode();
|
|
||||||
|
|
||||||
bool get_allow_list(int *uids, int *size);
|
|
||||||
|
|
||||||
bool uid_should_umount(int uid);
|
|
||||||
|
|
||||||
bool is_safe_mode();
|
|
||||||
|
|
||||||
bool is_lkm_mode();
|
|
||||||
|
|
||||||
#define KSU_APP_PROFILE_VER 2
|
|
||||||
#define KSU_MAX_PACKAGE_NAME 256
|
|
||||||
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
|
||||||
#define KSU_MAX_GROUPS 32
|
|
||||||
#define KSU_SELINUX_DOMAIN 64
|
|
||||||
|
|
||||||
using p_key_t = char[KSU_MAX_PACKAGE_NAME];
|
|
||||||
|
|
||||||
struct root_profile {
|
|
||||||
int32_t uid;
|
|
||||||
int32_t gid;
|
|
||||||
|
|
||||||
int32_t groups_count;
|
|
||||||
int32_t groups[KSU_MAX_GROUPS];
|
|
||||||
|
|
||||||
// kernel_cap_t is u32[2] for capabilities v3
|
|
||||||
struct {
|
|
||||||
uint64_t effective;
|
|
||||||
uint64_t permitted;
|
|
||||||
uint64_t inheritable;
|
|
||||||
} capabilities;
|
|
||||||
|
|
||||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
|
||||||
|
|
||||||
int32_t namespaces;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct non_root_profile {
|
|
||||||
bool umount_modules;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct app_profile {
|
|
||||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
|
||||||
uint32_t version;
|
|
||||||
|
|
||||||
// this is usually the package of the app, but can be other value for special apps
|
|
||||||
char key[KSU_MAX_PACKAGE_NAME];
|
|
||||||
int32_t current_uid;
|
|
||||||
bool allow_su;
|
|
||||||
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
bool use_default;
|
|
||||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
|
||||||
|
|
||||||
struct root_profile profile;
|
|
||||||
} rp_config;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
bool use_default;
|
|
||||||
|
|
||||||
struct non_root_profile profile;
|
|
||||||
} nrp_config;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
bool set_app_profile(const app_profile *profile);
|
|
||||||
|
|
||||||
bool get_app_profile(p_key_t key, app_profile *profile);
|
|
||||||
|
|
||||||
bool set_su_enabled(bool enabled);
|
|
||||||
|
|
||||||
bool is_su_enabled();
|
|
||||||
|
|
||||||
bool is_zygisk_enabled();
|
|
||||||
|
|
||||||
#endif //KERNELSU_KSU_H
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package com.rifsxd.ksunext
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.system.Os
|
|
||||||
import coil.Coil
|
|
||||||
import coil.ImageLoader
|
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
|
||||||
import okhttp3.Cache
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import java.io.File
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
lateinit var ksuApp: KernelSUApplication
|
|
||||||
|
|
||||||
class KernelSUApplication : Application() {
|
|
||||||
|
|
||||||
lateinit var okhttpClient: OkHttpClient
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
ksuApp = this
|
|
||||||
|
|
||||||
val context = this
|
|
||||||
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
|
|
||||||
Coil.setImageLoader(
|
|
||||||
ImageLoader.Builder(context)
|
|
||||||
.components {
|
|
||||||
add(AppIconKeyer())
|
|
||||||
add(AppIconFetcher.Factory(iconSize, false, context))
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
|
|
||||||
val webroot = File(dataDir, "webroot")
|
|
||||||
if (!webroot.exists()) {
|
|
||||||
webroot.mkdir()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provide working env for rust's temp_dir()
|
|
||||||
Os.setenv("TMPDIR", cacheDir.absolutePath, true)
|
|
||||||
|
|
||||||
okhttpClient =
|
|
||||||
OkHttpClient.Builder().cache(Cache(File(cacheDir, "okhttp"), 10 * 1024 * 1024))
|
|
||||||
.addInterceptor { block ->
|
|
||||||
block.proceed(
|
|
||||||
block.request().newBuilder()
|
|
||||||
.header("User-Agent", "KernelSU/${BuildConfig.VERSION_CODE}")
|
|
||||||
.header("Accept-Language", Locale.getDefault().toLanguageTag()).build()
|
|
||||||
)
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.rifsxd.ksunext
|
|
||||||
|
|
||||||
import android.system.Os
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2022/12/10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
data class KernelVersion(val major: Int, val patchLevel: Int, val subLevel: Int) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "$major.$patchLevel.$subLevel"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isGKI(): Boolean {
|
|
||||||
|
|
||||||
// kernel 6.x
|
|
||||||
if (major > 5) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// kernel 5.10.x
|
|
||||||
if (major == 5) {
|
|
||||||
return patchLevel >= 10
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isULegacy(): Boolean {
|
|
||||||
return major == 3
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isLegacy(): Boolean {
|
|
||||||
return major == 4 && patchLevel in 1..18
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isGKI1(): Boolean {
|
|
||||||
return (major == 4 && patchLevel >= 19) || (major == 5 && patchLevel < 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseKernelVersion(version: String): KernelVersion {
|
|
||||||
val find = "(\\d+)\\.(\\d+)\\.(\\d+)".toRegex().find(version)
|
|
||||||
return if (find != null) {
|
|
||||||
KernelVersion(find.groupValues[1].toInt(), find.groupValues[2].toInt(), find.groupValues[3].toInt())
|
|
||||||
} else {
|
|
||||||
KernelVersion(-1, -1, -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getKernelVersion(): KernelVersion {
|
|
||||||
Os.uname().release.let {
|
|
||||||
return parseKernelVersion(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.os.UserManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.topjohnwu.superuser.ipc.RootService;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.rifsxd.ksunext.IKsuInterface;
|
|
||||||
import rikka.parcelablelist.ParcelableListSlice;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/4/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class KsuService extends RootService {
|
|
||||||
|
|
||||||
private static final String TAG = "KsuService";
|
|
||||||
|
|
||||||
class Stub extends IKsuInterface.Stub {
|
|
||||||
@Override
|
|
||||||
public ParcelableListSlice<PackageInfo> getPackages(int flags) {
|
|
||||||
List<PackageInfo> list = getInstalledPackagesAll(flags);
|
|
||||||
Log.i(TAG, "getPackages: " + list.size());
|
|
||||||
return new ParcelableListSlice<>(list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(@NonNull Intent intent) {
|
|
||||||
return new Stub();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Integer> getUserIds() {
|
|
||||||
List<Integer> result = new ArrayList<>();
|
|
||||||
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
|
|
||||||
List<UserHandle> userProfiles = um.getUserProfiles();
|
|
||||||
for (UserHandle userProfile : userProfiles) {
|
|
||||||
int userId = userProfile.hashCode();
|
|
||||||
result.add(userProfile.hashCode());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {
|
|
||||||
ArrayList<PackageInfo> packages = new ArrayList<>();
|
|
||||||
for (Integer userId : getUserIds()) {
|
|
||||||
Log.i(TAG, "getInstalledPackagesAll: " + userId);
|
|
||||||
packages.addAll(getInstalledPackagesAsUser(flags, userId));
|
|
||||||
}
|
|
||||||
return packages;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
|
|
||||||
try {
|
|
||||||
PackageManager pm = getPackageManager();
|
|
||||||
Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class);
|
|
||||||
return (List<PackageInfo>) getInstalledPackagesAsUser.invoke(pm, flags, userId);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Log.e(TAG, "err", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
package com.rifsxd.ksunext
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2022/12/8.
|
|
||||||
*/
|
|
||||||
object Natives {
|
|
||||||
// minimal supported kernel version
|
|
||||||
// 10915: allowlist breaking change, add app profile
|
|
||||||
// 10931: app profile struct add 'version' field
|
|
||||||
// 10946: add capabilities
|
|
||||||
// 10977: change groups_count and groups to avoid overflow write
|
|
||||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
|
||||||
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
|
||||||
|
|
||||||
// 11640: Support query working mode, LKM or GKI
|
|
||||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
|
||||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
|
||||||
|
|
||||||
// 12404: Support disable sucompat mode
|
|
||||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12404
|
|
||||||
|
|
||||||
// 12569: support get hook mode
|
|
||||||
const val MINIMAL_SUPPORTED_HOOK_MODE = 12569
|
|
||||||
|
|
||||||
// 12750: support get manager UID
|
|
||||||
const val MINIMAL_SUPPORTED_MANAGER_UID = 12751
|
|
||||||
|
|
||||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
|
||||||
|
|
||||||
const val ROOT_UID = 0
|
|
||||||
const val ROOT_GID = 0
|
|
||||||
|
|
||||||
init {
|
|
||||||
System.loadLibrary("kernelsu")
|
|
||||||
}
|
|
||||||
|
|
||||||
// become root manager, return true if success.
|
|
||||||
external fun becomeManager(pkg: String?): Boolean
|
|
||||||
val version: Int
|
|
||||||
external get
|
|
||||||
|
|
||||||
// get the uid list of allowed su processes.
|
|
||||||
val allowList: IntArray
|
|
||||||
external get
|
|
||||||
|
|
||||||
val isSafeMode: Boolean
|
|
||||||
external get
|
|
||||||
|
|
||||||
val isLkmMode: Boolean
|
|
||||||
external get
|
|
||||||
|
|
||||||
external fun uidShouldUmount(uid: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the UID of the current root manager.
|
|
||||||
* @return manager UID, or 0 if unavailable.
|
|
||||||
*/
|
|
||||||
external fun getManagerUid(): Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a string indicating the SU hook mode enabled in kernel.
|
|
||||||
* The return values are:
|
|
||||||
* - "Manual": Manual hooks was enabled.
|
|
||||||
* - "Kprobes": Kprobes hooks was enabled (CONFIG_KSU_KPROBES_HOOK).
|
|
||||||
*
|
|
||||||
* @return return hook mode, or null if unavailable.
|
|
||||||
*/
|
|
||||||
external fun getHookMode(): String?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if Zygisk injection is enabled in the environment.
|
|
||||||
*/
|
|
||||||
external fun isZygiskEnabled(): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the profile of the given package.
|
|
||||||
* @param key usually the package name
|
|
||||||
* @return return null if failed.
|
|
||||||
*/
|
|
||||||
external fun getAppProfile(key: String?, uid: Int): Profile
|
|
||||||
external fun setAppProfile(profile: Profile?): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `su` compat mode can be disabled temporarily.
|
|
||||||
* 0: disabled
|
|
||||||
* 1: enabled
|
|
||||||
* negative : error
|
|
||||||
*/
|
|
||||||
external fun isSuEnabled(): Boolean
|
|
||||||
external fun setSuEnabled(enabled: Boolean): Boolean
|
|
||||||
|
|
||||||
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
|
|
||||||
private const val NOBODY_UID = 9999
|
|
||||||
|
|
||||||
fun setDefaultUmountModules(umountModules: Boolean): Boolean {
|
|
||||||
Profile(
|
|
||||||
NON_ROOT_DEFAULT_PROFILE_KEY,
|
|
||||||
NOBODY_UID,
|
|
||||||
false,
|
|
||||||
umountModules = umountModules
|
|
||||||
).let {
|
|
||||||
return setAppProfile(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isDefaultUmountModules(): Boolean {
|
|
||||||
getAppProfile(NON_ROOT_DEFAULT_PROFILE_KEY, NOBODY_UID).let {
|
|
||||||
return it.umountModules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requireNewKernel(): Boolean {
|
|
||||||
return version < MINIMAL_SUPPORTED_KERNEL
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@Parcelize
|
|
||||||
@Keep
|
|
||||||
data class Profile(
|
|
||||||
// and there is a default profile for root and non-root
|
|
||||||
val name: String,
|
|
||||||
// current uid for the package, this is convivent for kernel to check
|
|
||||||
// if the package name doesn't match uid, then it should be invalidated.
|
|
||||||
val currentUid: Int = 0,
|
|
||||||
|
|
||||||
// if this is true, kernel will grant root permission to this package
|
|
||||||
val allowSu: Boolean = false,
|
|
||||||
|
|
||||||
// these are used for root profile
|
|
||||||
val rootUseDefault: Boolean = true,
|
|
||||||
val rootTemplate: String? = null,
|
|
||||||
val uid: Int = ROOT_UID,
|
|
||||||
val gid: Int = ROOT_GID,
|
|
||||||
val groups: List<Int> = mutableListOf(),
|
|
||||||
val capabilities: List<Int> = mutableListOf(),
|
|
||||||
val context: String = KERNEL_SU_DOMAIN,
|
|
||||||
val namespace: Int = Namespace.INHERITED.ordinal,
|
|
||||||
|
|
||||||
val nonRootUseDefault: Boolean = true,
|
|
||||||
val umountModules: Boolean = true,
|
|
||||||
var rules: String = "", // this field is save in ksud!!
|
|
||||||
) : Parcelable {
|
|
||||||
enum class Namespace {
|
|
||||||
INHERITED,
|
|
||||||
GLOBAL,
|
|
||||||
INDIVIDUAL,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() : this("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.profile
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/6/3.
|
|
||||||
*/
|
|
||||||
enum class Capabilities(val cap: Int, val display: String, val desc: String) {
|
|
||||||
CAP_CHOWN(0, "CHOWN", "Make arbitrary changes to file UIDs and GIDs (see chown(2))"),
|
|
||||||
CAP_DAC_OVERRIDE(1, "DAC_OVERRIDE", "Bypass file read, write, and execute permission checks"),
|
|
||||||
CAP_DAC_READ_SEARCH(2, "DAC_READ_SEARCH", "Bypass file read permission checks and directory read and execute permission checks"),
|
|
||||||
CAP_FOWNER(3, "FOWNER", "Bypass permission checks on operations that normally require the filesystem UID of the process to match the UID of the file (e.g., chmod(2), utime(2)), excluding those operations covered by CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH"),
|
|
||||||
CAP_FSETID(4, "FSETID", "Don’t clear set-user-ID and set-group-ID permission bits when a file is modified; set the set-group-ID bit for a file whose GID does not match the filesystem or any of the supplementary GIDs of the calling process"),
|
|
||||||
CAP_KILL(5, "KILL", "Bypass permission checks for sending signals (see kill(2))."),
|
|
||||||
CAP_SETGID(6, "SETGID", "Make arbitrary manipulations of process GIDs and supplementary GID list; allow setgid(2) manipulation of the caller’s effective and real group IDs"),
|
|
||||||
CAP_SETUID(7, "SETUID", "Make arbitrary manipulations of process UIDs (setuid(2), setreuid(2), setresuid(2), setfsuid(2)); allow changing the current process user IDs; allow changing of the current process group ID to any value in the system’s range of legal group IDs"),
|
|
||||||
CAP_SETPCAP(8, "SETPCAP", "If file capabilities are supported: grant or remove any capability in the caller’s permitted capability set to or from any other process. (This property supersedes the obsolete notion of giving a process all capabilities by granting all capabilities in its permitted set, and of removing all capabilities from a process by granting no capabilities in its permitted set. It does not permit any actions that were not permitted before.)"),
|
|
||||||
CAP_LINUX_IMMUTABLE(9, "LINUX_IMMUTABLE", "Set the FS_APPEND_FL and FS_IMMUTABLE_FL inode flags (see chattr(1))."),
|
|
||||||
CAP_NET_BIND_SERVICE(10, "NET_BIND_SERVICE", "Bind a socket to Internet domain"),
|
|
||||||
CAP_NET_BROADCAST(11, "NET_BROADCAST", "Make socket broadcasts, and listen to multicasts"),
|
|
||||||
CAP_NET_ADMIN(12, "NET_ADMIN", "Perform various network-related operations: interface configuration, administration of IP firewall, masquerading, and accounting, modify routing tables, bind to any address for transparent proxying, set type-of-service (TOS), clear driver statistics, set promiscuous mode, enabling multicasting, use setsockopt(2) to set the following socket options: SO_DEBUG, SO_MARK, SO_PRIORITY (for a priority outside the range 0 to 6), SO_RCVBUFFORCE, and SO_SNDBUFFORCE"),
|
|
||||||
CAP_NET_RAW(13, "NET_RAW", "Use RAW and PACKET sockets"),
|
|
||||||
CAP_IPC_LOCK(14, "IPC_LOCK", "Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2))"),
|
|
||||||
CAP_IPC_OWNER(15, "IPC_OWNER", "Bypass permission checks for operations on System V IPC objects"),
|
|
||||||
CAP_SYS_MODULE(16, "SYS_MODULE", "Load and unload kernel modules (see init_module(2) and delete_module(2)); in kernels before 2.6.25, this also granted rights for various other operations related to kernel modules"),
|
|
||||||
CAP_SYS_RAWIO(17, "SYS_RAWIO", "Perform I/O port operations (iopl(2) and ioperm(2)); access /proc/kcore"),
|
|
||||||
CAP_SYS_CHROOT(18, "SYS_CHROOT", "Use chroot(2)"),
|
|
||||||
CAP_SYS_PTRACE(19, "SYS_PTRACE", "Trace arbitrary processes using ptrace(2)"),
|
|
||||||
CAP_SYS_PACCT(20, "SYS_PACCT", "Use acct(2)"),
|
|
||||||
CAP_SYS_ADMIN(21, "SYS_ADMIN", "Perform a range of system administration operations including: quotactl(2), mount(2), umount(2), swapon(2), swapoff(2), sethostname(2), and setdomainname(2); set and modify process resource limits (setrlimit(2)); perform various network-related operations (e.g., setting privileged socket options, enabling multicasting, interface configuration); perform various IPC operations (e.g., SysV semaphores, POSIX message queues, System V shared memory); allow reboot and kexec_load(2); override /proc/sys kernel tunables; perform ptrace(2) PTRACE_SECCOMP_GET_FILTER operation; perform some tracing and debugging operations (see ptrace(2)); administer the lifetime of kernel tracepoints (tracefs(5)); perform the KEYCTL_CHOWN and KEYCTL_SETPERM keyctl(2) operations; perform the following keyctl(2) operations: KEYCTL_CAPABILITIES, KEYCTL_CAPSQUASH, and KEYCTL_PKEY_ OPERATIONS; set state for the Extensible Authentication Protocol (EAP) kernel module; and override the RLIMIT_NPROC resource limit; allow ioperm/iopl access to I/O ports"),
|
|
||||||
CAP_SYS_BOOT(22, "SYS_BOOT", "Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution"),
|
|
||||||
CAP_SYS_NICE(23, "SYS_NICE", "Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes; set real-time scheduling policies for calling process, and set scheduling policies and priorities for arbitrary processes (sched_setscheduler(2), sched_setparam(2)"),
|
|
||||||
CAP_SYS_RESOURCE(24, "SYS_RESOURCE", "Override resource Limits. Set resource limits (setrlimit(2), prlimit(2)), override quota limits (quota(2), quotactl(2)), override reserved space on ext2 filesystem (ext2_ioctl(2)), override size restrictions on IPC message queues (msg(2)) and system V shared memory segments (shmget(2)), and override the /proc/sys/fs/pipe-size-max limit"),
|
|
||||||
CAP_SYS_TIME(25, "SYS_TIME", "Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock"),
|
|
||||||
CAP_SYS_TTY_CONFIG(26, "SYS_TTY_CONFIG", "Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals"),
|
|
||||||
CAP_MKNOD(27, "MKNOD", "Create special files using mknod(2)"),
|
|
||||||
CAP_LEASE(28, "LEASE", "Establish leases on arbitrary files (see fcntl(2))"),
|
|
||||||
CAP_AUDIT_WRITE(29, "AUDIT_WRITE", "Write records to kernel auditing log"),
|
|
||||||
CAP_AUDIT_CONTROL(30, "AUDIT_CONTROL", "Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules"),
|
|
||||||
CAP_SETFCAP(31, "SETFCAP", "If file capabilities are supported: grant or remove any capability in any capability set to any file"),
|
|
||||||
CAP_MAC_OVERRIDE(32, "MAC_OVERRIDE", "Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM)"),
|
|
||||||
CAP_MAC_ADMIN(33, "MAC_ADMIN", "Allow MAC configuration or state changes. Implemented for the Smack LSM"),
|
|
||||||
CAP_SYSLOG(34, "SYSLOG", "Perform privileged syslog(2) operations. See syslog(2) for information on which operations require privilege"),
|
|
||||||
CAP_WAKE_ALARM(35, "WAKE_ALARM", "Trigger something that will wake up the system"),
|
|
||||||
CAP_BLOCK_SUSPEND(36, "BLOCK_SUSPEND", "Employ features that can block system suspend"),
|
|
||||||
CAP_AUDIT_READ(37, "AUDIT_READ", "Allow reading the audit log via a multicast netlink socket"),
|
|
||||||
CAP_PERFMON(38, "PERFMON", "Allow performance monitoring via perf_event_open(2)"),
|
|
||||||
CAP_BPF(39, "BPF", "Allow BPF operations via bpf(2)"),
|
|
||||||
CAP_CHECKPOINT_RESTORE(40, "CHECKPOINT_RESTORE", "Allow processes to be checkpointed via checkpoint/restore in user namespace(2)"),
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.profile
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/6/3.
|
|
||||||
*/
|
|
||||||
enum class Groups(val gid: Int, val display: String, val desc: String) {
|
|
||||||
ROOT(0, "root", "traditional unix root user"),
|
|
||||||
DAEMON(1, "daemon", "Traditional unix daemon owner."),
|
|
||||||
BIN(2, "bin", "Traditional unix binaries owner."),
|
|
||||||
SYS(3, "sys", "A group with the same gid on Linux/macOS/Android."),
|
|
||||||
SYSTEM(1000, "system", "system server"),
|
|
||||||
RADIO(1001, "radio", "telephony subsystem, RIL"),
|
|
||||||
BLUETOOTH(1002, "bluetooth", "bluetooth subsystem"),
|
|
||||||
GRAPHICS(1003, "graphics", "graphics devices"),
|
|
||||||
INPUT(1004, "input", "input devices"),
|
|
||||||
AUDIO(1005, "audio", "audio devices"),
|
|
||||||
CAMERA(1006, "camera", "camera devices"),
|
|
||||||
LOG(1007, "log", "log devices"),
|
|
||||||
COMPASS(1008, "compass", "compass device"),
|
|
||||||
MOUNT(1009, "mount", "mountd socket"),
|
|
||||||
WIFI(1010, "wifi", "wifi subsystem"),
|
|
||||||
ADB(1011, "adb", "android debug bridge (adbd)"),
|
|
||||||
INSTALL(1012, "install", "group for installing packages"),
|
|
||||||
MEDIA(1013, "media", "mediaserver process"),
|
|
||||||
DHCP(1014, "dhcp", "dhcp client"),
|
|
||||||
SDCARD_RW(1015, "sdcard_rw", "external storage write access"),
|
|
||||||
VPN(1016, "vpn", "vpn system"),
|
|
||||||
KEYSTORE(1017, "keystore", "keystore subsystem"),
|
|
||||||
USB(1018, "usb", "USB devices"),
|
|
||||||
DRM(1019, "drm", "DRM server"),
|
|
||||||
MDNSR(1020, "mdnsr", "MulticastDNSResponder (service discovery)"),
|
|
||||||
GPS(1021, "gps", "GPS daemon"),
|
|
||||||
UNUSED1(1022, "unused1", "deprecated, DO NOT USE"),
|
|
||||||
MEDIA_RW(1023, "media_rw", "internal media storage write access"),
|
|
||||||
MTP(1024, "mtp", "MTP USB driver access"),
|
|
||||||
UNUSED2(1025, "unused2", "deprecated, DO NOT USE"),
|
|
||||||
DRMRPC(1026, "drmrpc", "group for drm rpc"),
|
|
||||||
NFC(1027, "nfc", "nfc subsystem"),
|
|
||||||
SDCARD_R(1028, "sdcard_r", "external storage read access"),
|
|
||||||
CLAT(1029, "clat", "clat part of nat464"),
|
|
||||||
LOOP_RADIO(1030, "loop_radio", "loop radio devices"),
|
|
||||||
MEDIA_DRM(1031, "media_drm", "MediaDrm plugins"),
|
|
||||||
PACKAGE_INFO(1032, "package_info", "access to installed package details"),
|
|
||||||
SDCARD_PICS(1033, "sdcard_pics", "external storage photos access"),
|
|
||||||
SDCARD_AV(1034, "sdcard_av", "external storage audio/video access"),
|
|
||||||
SDCARD_ALL(1035, "sdcard_all", "access all users external storage"),
|
|
||||||
LOGD(1036, "logd", "log daemon"),
|
|
||||||
SHARED_RELRO(1037, "shared_relro", "creator of shared GNU RELRO files"),
|
|
||||||
DBUS(1038, "dbus", "dbus-daemon IPC broker process"),
|
|
||||||
TLSDATE(1039, "tlsdate", "tlsdate unprivileged user"),
|
|
||||||
MEDIA_EX(1040, "media_ex", "mediaextractor process"),
|
|
||||||
AUDIOSERVER(1041, "audioserver", "audioserver process"),
|
|
||||||
METRICS_COLL(1042, "metrics_coll", "metrics_collector process"),
|
|
||||||
METRICSD(1043, "metricsd", "metricsd process"),
|
|
||||||
WEBSERV(1044, "webserv", "webservd process"),
|
|
||||||
DEBUGGERD(1045, "debuggerd", "debuggerd unprivileged user"),
|
|
||||||
MEDIA_CODEC(1046, "media_codec", "media_codec process"),
|
|
||||||
CAMERASERVER(1047, "cameraserver", "cameraserver process"),
|
|
||||||
FIREWALL(1048, "firewall", "firewall process"),
|
|
||||||
TRUNKS(1049, "trunks", "trunksd process"),
|
|
||||||
NVRAM(1050, "nvram", "nvram daemon"),
|
|
||||||
DNS(1051, "dns", "DNS resolution daemon (system: netd)"),
|
|
||||||
DNS_TETHER(1052, "dns_tether", "DNS resolution daemon (tether: dnsmasq)"),
|
|
||||||
WEBVIEW_ZYGOTE(1053, "webview_zygote", "WebView zygote process"),
|
|
||||||
VEHICLE_NETWORK(1054, "vehicle_network", "Vehicle network service"),
|
|
||||||
MEDIA_AUDIO(1055, "media_audio", "GID for audio files on internal media storage"),
|
|
||||||
MEDIA_VIDEO(1056, "media_video", "GID for video files on internal media storage"),
|
|
||||||
MEDIA_IMAGE(1057, "media_image", "GID for image files on internal media storage"),
|
|
||||||
TOMBSTONED(1058, "tombstoned", "tombstoned user"),
|
|
||||||
MEDIA_OBB(1059, "media_obb", "GID for OBB files on internal media storage"),
|
|
||||||
ESE(1060, "ese", "embedded secure element (eSE) subsystem"),
|
|
||||||
OTA_UPDATE(1061, "ota_update", "resource tracking UID for OTA updates"),
|
|
||||||
AUTOMOTIVE_EVS(1062, "automotive_evs", "Automotive rear and surround view system"),
|
|
||||||
LOWPAN(1063, "lowpan", "LoWPAN subsystem"),
|
|
||||||
HSM(1064, "lowpan", "hardware security module subsystem"),
|
|
||||||
RESERVED_DISK(1065, "reserved_disk", "GID that has access to reserved disk space"),
|
|
||||||
STATSD(1066, "statsd", "statsd daemon"),
|
|
||||||
INCIDENTD(1067, "incidentd", "incidentd daemon"),
|
|
||||||
SECURE_ELEMENT(1068, "secure_element", "secure element subsystem"),
|
|
||||||
LMKD(1069, "lmkd", "low memory killer daemon"),
|
|
||||||
LLKD(1070, "llkd", "live lock daemon"),
|
|
||||||
IORAPD(1071, "iorapd", "input/output readahead and pin daemon"),
|
|
||||||
GPU_SERVICE(1072, "gpu_service", "GPU service daemon"),
|
|
||||||
NETWORK_STACK(1073, "network_stack", "network stack service"),
|
|
||||||
GSID(1074, "GSID", "GSI service daemon"),
|
|
||||||
FSVERITY_CERT(1075, "fsverity_cert", "fs-verity key ownership in keystore"),
|
|
||||||
CREDSTORE(1076, "credstore", "identity credential manager service"),
|
|
||||||
EXTERNAL_STORAGE(1077, "external_storage", "Full external storage access including USB OTG volumes"),
|
|
||||||
EXT_DATA_RW(1078, "ext_data_rw", "GID for app-private data directories on external storage"),
|
|
||||||
EXT_OBB_RW(1079, "ext_obb_rw", "GID for OBB directories on external storage"),
|
|
||||||
CONTEXT_HUB(1080, "context_hub", "GID for access to the Context Hub"),
|
|
||||||
VIRTUALIZATIONSERVICE(1081, "virtualizationservice", "VirtualizationService daemon"),
|
|
||||||
ARTD(1082, "artd", "ART Service daemon"),
|
|
||||||
UWB(1083, "uwb", "UWB subsystem"),
|
|
||||||
THREAD_NETWORK(1084, "thread_network", "Thread Network subsystem"),
|
|
||||||
DICED(1085, "diced", "Android's DICE daemon"),
|
|
||||||
DMESGD(1086, "dmesgd", "dmesg parsing daemon for kernel report collection"),
|
|
||||||
JC_WEAVER(1087, "jc_weaver", "Javacard Weaver HAL - to manage omapi ARA rules"),
|
|
||||||
JC_STRONGBOX(1088, "jc_strongbox", "Javacard Strongbox HAL - to manage omapi ARA rules"),
|
|
||||||
JC_IDENTITYCRED(1089, "jc_identitycred", "Javacard Identity Cred HAL - to manage omapi ARA rules"),
|
|
||||||
SDK_SANDBOX(1090, "sdk_sandbox", "SDK sandbox virtual UID"),
|
|
||||||
SECURITY_LOG_WRITER(1091, "security_log_writer", "write to security log"),
|
|
||||||
PRNG_SEEDER(1092, "prng_seeder", "PRNG seeder daemon"),
|
|
||||||
|
|
||||||
SHELL(2000, "shell", "adb and debug shell user"),
|
|
||||||
CACHE(2001, "cache", "cache access"),
|
|
||||||
DIAG(2002, "diag", "access to diagnostic resources"),
|
|
||||||
|
|
||||||
/* The 3000 series are intended for use as supplemental group id's only.
|
|
||||||
* They indicate special Android capabilities that the kernel is aware of. */
|
|
||||||
NET_BT_ADMIN(3001, "net_bt_admin", "bluetooth: create any socket"),
|
|
||||||
NET_BT(3002, "net_bt", "bluetooth: create sco, rfcomm or l2cap sockets"),
|
|
||||||
INET(3003, "inet", "can create AF_INET and AF_INET6 sockets"),
|
|
||||||
NET_RAW(3004, "net_raw", "can create raw INET sockets"),
|
|
||||||
NET_ADMIN(3005, "net_admin", "can configure interfaces and routing tables."),
|
|
||||||
NET_BW_STATS(3006, "net_bw_stats", "read bandwidth statistics"),
|
|
||||||
NET_BW_ACCT(3007, "net_bw_acct", "change bandwidth statistics accounting"),
|
|
||||||
NET_BT_STACK(3008, "net_bt_stack", "access to various bluetooth management functions"),
|
|
||||||
READPROC(3009, "readproc", "Allow /proc read access"),
|
|
||||||
WAKELOCK(3010, "wakelock", "Allow system wakelock read/write access"),
|
|
||||||
UHID(3011, "uhid", "Allow read/write to /dev/uhid node"),
|
|
||||||
READTRACEFS(3012, "readtracefs", "Allow tracefs read"),
|
|
||||||
|
|
||||||
EVERYBODY(9997, "everybody", "Shared external storage read/write"),
|
|
||||||
MISC(9998, "misc", "Access to misc storage"),
|
|
||||||
NOBODY(9999, "nobody", "Reserved"),
|
|
||||||
APP(10000, "app", "Access to app data"),
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.slideInVertically
|
|
||||||
import androidx.compose.animation.slideOutVertically
|
|
||||||
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.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
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.Badge
|
|
||||||
import androidx.compose.material3.BadgedBox
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
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.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.NavBackStackEntry
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
|
||||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
|
||||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
|
||||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
|
||||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.ksuApp
|
|
||||||
import com.rifsxd.ksunext.ui.screen.BottomBarDestination
|
|
||||||
import com.rifsxd.ksunext.ui.theme.KernelSUTheme
|
|
||||||
import com.rifsxd.ksunext.ui.util.*
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocaleHelper
|
|
||||||
import com.rifsxd.ksunext.ui.util.rootAvailable
|
|
||||||
import com.rifsxd.ksunext.ui.util.install
|
|
||||||
import com.rifsxd.ksunext.ui.util.isSuCompatDisabled
|
|
||||||
import com.rifsxd.ksunext.ui.screen.FlashIt
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.SuperUserViewModel
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
|
||||||
|
|
||||||
override fun attachBaseContext(newBase: Context?) {
|
|
||||||
super.attachBaseContext(newBase?.let { LocaleHelper.applyLanguage(it) })
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
|
|
||||||
// Enable edge to edge
|
|
||||||
enableEdgeToEdge()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
window.isNavigationBarContrastEnforced = false
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
|
||||||
if (isManager) install()
|
|
||||||
|
|
||||||
val zipUri: Uri? = when (intent?.action) {
|
|
||||||
Intent.ACTION_VIEW, Intent.ACTION_SEND -> {
|
|
||||||
val uri = intent.data ?: intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
|
||||||
uri?.let {
|
|
||||||
val name = when (it.scheme) {
|
|
||||||
"file" -> it.lastPathSegment ?: ""
|
|
||||||
"content" -> {
|
|
||||||
contentResolver.query(it, null, null, null, null)?.use { cursor ->
|
|
||||||
val nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
|
|
||||||
if (cursor.moveToFirst() && nameIndex != -1) {
|
|
||||||
cursor.getString(nameIndex)
|
|
||||||
} else {
|
|
||||||
it.lastPathSegment ?: ""
|
|
||||||
}
|
|
||||||
} ?: (it.lastPathSegment ?: "")
|
|
||||||
}
|
|
||||||
else -> it.lastPathSegment ?: ""
|
|
||||||
}
|
|
||||||
if (name.lowercase().endsWith(".zip")) it else null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent {
|
|
||||||
// Read AMOLED mode preference
|
|
||||||
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
val amoledMode = prefs.getBoolean("enable_amoled", false)
|
|
||||||
|
|
||||||
val moduleViewModel: ModuleViewModel = viewModel()
|
|
||||||
val superUserViewModel: SuperUserViewModel = viewModel()
|
|
||||||
val moduleUpdateCount = moduleViewModel.moduleList.count {
|
|
||||||
moduleViewModel.checkUpdate(it).first.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
KernelSUTheme (
|
|
||||||
amoledMode = amoledMode
|
|
||||||
) {
|
|
||||||
val navController = rememberNavController()
|
|
||||||
val snackBarHostState = remember { SnackbarHostState() }
|
|
||||||
val currentDestination = navController.currentBackStackEntryAsState()?.value?.destination
|
|
||||||
|
|
||||||
val navigator = navController.rememberDestinationsNavigator()
|
|
||||||
|
|
||||||
LaunchedEffect(zipUri) {
|
|
||||||
if (zipUri != null) {
|
|
||||||
navigator.navigate(
|
|
||||||
FlashScreenDestination(
|
|
||||||
FlashIt.FlashModules(listOf(zipUri)),
|
|
||||||
finishIntent = true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (superUserViewModel.appList.isEmpty()) {
|
|
||||||
superUserViewModel.fetchAppList()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moduleViewModel.moduleList.isEmpty()) {
|
|
||||||
moduleViewModel.fetchModuleList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val showBottomBar = when (currentDestination?.route) {
|
|
||||||
FlashScreenDestination.route -> false // Hide for FlashScreenDestination
|
|
||||||
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
bottomBar = {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = showBottomBar,
|
|
||||||
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
|
||||||
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
|
||||||
) {
|
|
||||||
BottomBar(navController, moduleUpdateCount)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
|
||||||
) { innerPadding ->
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalSnackbarHost provides snackBarHostState,
|
|
||||||
) {
|
|
||||||
DestinationsNavHost(
|
|
||||||
modifier = Modifier.padding(innerPadding),
|
|
||||||
navGraph = NavGraphs.root,
|
|
||||||
navController = navController,
|
|
||||||
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
|
||||||
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
|
|
||||||
get() = { fadeIn(animationSpec = tween(340)) }
|
|
||||||
override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
|
|
||||||
get() = { fadeOut(animationSpec = tween(340)) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun BottomBar(navController: NavHostController, moduleUpdateCount: Int) {
|
|
||||||
val navigator = navController.rememberDestinationsNavigator()
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
|
||||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
|
||||||
val suCompatDisabled = isSuCompatDisabled()
|
|
||||||
val suSFS = getSuSFS()
|
|
||||||
val susSUMode = susfsSUS_SU_Mode()
|
|
||||||
|
|
||||||
NavigationBar(
|
|
||||||
tonalElevation = 8.dp,
|
|
||||||
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
|
||||||
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
BottomBarDestination.entries
|
|
||||||
.forEach { destination ->
|
|
||||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
|
||||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
|
||||||
NavigationBarItem(
|
|
||||||
selected = isCurrentDestOnBackStack,
|
|
||||||
onClick = {
|
|
||||||
if (isCurrentDestOnBackStack) {
|
|
||||||
navigator.popBackStack(destination.direction, false)
|
|
||||||
}
|
|
||||||
navigator.navigate(destination.direction) {
|
|
||||||
popUpTo(NavGraphs.root) {
|
|
||||||
saveState = true
|
|
||||||
}
|
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = {
|
|
||||||
// Show badge for Module icon if moduleUpdateCount > 0
|
|
||||||
if (destination == BottomBarDestination.Module && moduleUpdateCount > 0) {
|
|
||||||
BadgedBox(badge = { Badge { Text(moduleUpdateCount.toString()) } }) {
|
|
||||||
if (isCurrentDestOnBackStack) {
|
|
||||||
Icon(destination.iconSelected, stringResource(destination.label))
|
|
||||||
} else {
|
|
||||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isCurrentDestOnBackStack) {
|
|
||||||
Icon(destination.iconSelected, stringResource(destination.label))
|
|
||||||
} else {
|
|
||||||
Icon(destination.iconNotSelected, stringResource(destination.label))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label = { Text(stringResource(destination.label)) },
|
|
||||||
alwaysShowLabel = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.component
|
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.ElevatedCard
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.scale
|
|
||||||
import androidx.compose.ui.res.colorResource
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
|
||||||
import androidx.compose.ui.text.TextLinkStyles
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.fromHtml
|
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import com.rifsxd.ksunext.BuildConfig
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun AboutCard() {
|
|
||||||
ElevatedCard(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
shape = RoundedCornerShape(8.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(24.dp)
|
|
||||||
) {
|
|
||||||
AboutCardContent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AboutDialog(dismiss: () -> Unit) {
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = { dismiss() }
|
|
||||||
) {
|
|
||||||
AboutCard()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AboutCardContent() {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Row {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.size(40.dp),
|
|
||||||
color = colorResource(id = R.color.ic_launcher_background),
|
|
||||||
shape = CircleShape
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.ic_launcher_foreground),
|
|
||||||
contentDescription = "icon",
|
|
||||||
modifier = Modifier.scale(1.5f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
|
|
||||||
Column {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.app_name),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
fontSize = 18.sp
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
BuildConfig.VERSION_NAME,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
fontSize = 14.sp
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
val annotatedString = AnnotatedString.Companion.fromHtml(
|
|
||||||
htmlString = stringResource(
|
|
||||||
id = R.string.about_source_code,
|
|
||||||
"<b><a href=\"https://github.com/KernelSU-Next/KernelSU-Next\">GitHub</a></b>"
|
|
||||||
),
|
|
||||||
linkStyles = TextLinkStyles(
|
|
||||||
style = SpanStyle(
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
textDecoration = TextDecoration.Underline
|
|
||||||
),
|
|
||||||
pressedStyle = SpanStyle(
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
background = MaterialTheme.colorScheme.secondaryContainer,
|
|
||||||
textDecoration = TextDecoration.Underline
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = annotatedString,
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 14.sp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,458 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.component
|
|
||||||
|
|
||||||
import android.graphics.text.LineBreaker
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.text.Layout
|
|
||||||
import android.text.method.LinkMovementMethod
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.runtime.saveable.Saver
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import io.noties.markwon.Markwon
|
|
||||||
import io.noties.markwon.utils.NoCopySpannableFactory
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.channels.ReceiveChannel
|
|
||||||
import kotlinx.coroutines.flow.FlowCollector
|
|
||||||
import kotlinx.coroutines.flow.consumeAsFlow
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
|
|
||||||
private const val TAG = "DialogComponent"
|
|
||||||
|
|
||||||
interface ConfirmDialogVisuals : Parcelable {
|
|
||||||
val title: String
|
|
||||||
val content: String
|
|
||||||
val isMarkdown: Boolean
|
|
||||||
val confirm: String?
|
|
||||||
val dismiss: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
private data class ConfirmDialogVisualsImpl(
|
|
||||||
override val title: String,
|
|
||||||
override val content: String,
|
|
||||||
override val isMarkdown: Boolean,
|
|
||||||
override val confirm: String?,
|
|
||||||
override val dismiss: String?,
|
|
||||||
) : ConfirmDialogVisuals {
|
|
||||||
companion object {
|
|
||||||
val Empty: ConfirmDialogVisuals = ConfirmDialogVisualsImpl("", "", false, null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DialogHandle {
|
|
||||||
val isShown: Boolean
|
|
||||||
val dialogType: String
|
|
||||||
fun show()
|
|
||||||
fun hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoadingDialogHandle : DialogHandle {
|
|
||||||
suspend fun <R> withLoading(block: suspend () -> R): R
|
|
||||||
fun showLoading()
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface ConfirmResult {
|
|
||||||
object Confirmed : ConfirmResult
|
|
||||||
object Canceled : ConfirmResult
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ConfirmDialogHandle : DialogHandle {
|
|
||||||
val visuals: ConfirmDialogVisuals
|
|
||||||
|
|
||||||
fun showConfirm(
|
|
||||||
title: String,
|
|
||||||
content: String,
|
|
||||||
markdown: Boolean = false,
|
|
||||||
confirm: String? = null,
|
|
||||||
dismiss: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun awaitConfirm(
|
|
||||||
title: String,
|
|
||||||
content: String,
|
|
||||||
markdown: Boolean = false,
|
|
||||||
confirm: String? = null,
|
|
||||||
dismiss: String? = null
|
|
||||||
): ConfirmResult
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class DialogHandleBase(
|
|
||||||
val visible: MutableState<Boolean>,
|
|
||||||
val coroutineScope: CoroutineScope
|
|
||||||
) : DialogHandle {
|
|
||||||
override val isShown: Boolean
|
|
||||||
get() = visible.value
|
|
||||||
|
|
||||||
override fun show() {
|
|
||||||
coroutineScope.launch {
|
|
||||||
visible.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final override fun hide() {
|
|
||||||
coroutineScope.launch {
|
|
||||||
visible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return dialogType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LoadingDialogHandleImpl(
|
|
||||||
visible: MutableState<Boolean>,
|
|
||||||
coroutineScope: CoroutineScope
|
|
||||||
) : LoadingDialogHandle, DialogHandleBase(visible, coroutineScope) {
|
|
||||||
override suspend fun <R> withLoading(block: suspend () -> R): R {
|
|
||||||
return coroutineScope.async {
|
|
||||||
try {
|
|
||||||
visible.value = true
|
|
||||||
block()
|
|
||||||
} finally {
|
|
||||||
visible.value = false
|
|
||||||
}
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showLoading() {
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val dialogType: String get() = "LoadingDialog"
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias NullableCallback = (() -> Unit)?
|
|
||||||
|
|
||||||
interface ConfirmCallback {
|
|
||||||
|
|
||||||
val onConfirm: NullableCallback
|
|
||||||
|
|
||||||
val onDismiss: NullableCallback
|
|
||||||
|
|
||||||
val isEmpty: Boolean get() = onConfirm == null && onDismiss == null
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
operator fun invoke(onConfirmProvider: () -> NullableCallback, onDismissProvider: () -> NullableCallback): ConfirmCallback {
|
|
||||||
return object : ConfirmCallback {
|
|
||||||
override val onConfirm: NullableCallback
|
|
||||||
get() = onConfirmProvider()
|
|
||||||
override val onDismiss: NullableCallback
|
|
||||||
get() = onDismissProvider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ConfirmDialogHandleImpl(
|
|
||||||
visible: MutableState<Boolean>,
|
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
callback: ConfirmCallback,
|
|
||||||
override var visuals: ConfirmDialogVisuals = ConfirmDialogVisualsImpl.Empty,
|
|
||||||
private val resultFlow: ReceiveChannel<ConfirmResult>
|
|
||||||
) : ConfirmDialogHandle, DialogHandleBase(visible, coroutineScope) {
|
|
||||||
private class ResultCollector(
|
|
||||||
private val callback: ConfirmCallback
|
|
||||||
) : FlowCollector<ConfirmResult> {
|
|
||||||
fun handleResult(result: ConfirmResult) {
|
|
||||||
Log.d(TAG, "handleResult: ${result.javaClass.simpleName}")
|
|
||||||
when (result) {
|
|
||||||
ConfirmResult.Confirmed -> onConfirm()
|
|
||||||
ConfirmResult.Canceled -> onDismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onConfirm() {
|
|
||||||
callback.onConfirm?.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDismiss() {
|
|
||||||
callback.onDismiss?.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun emit(value: ConfirmResult) {
|
|
||||||
handleResult(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val resultCollector = ResultCollector(callback)
|
|
||||||
|
|
||||||
private var awaitContinuation: CancellableContinuation<ConfirmResult>? = null
|
|
||||||
|
|
||||||
private val isCallbackEmpty = callback.isEmpty
|
|
||||||
|
|
||||||
init {
|
|
||||||
coroutineScope.launch {
|
|
||||||
resultFlow
|
|
||||||
.consumeAsFlow()
|
|
||||||
.onEach { result ->
|
|
||||||
awaitContinuation?.let {
|
|
||||||
awaitContinuation = null
|
|
||||||
if (it.isActive) {
|
|
||||||
it.resume(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onEach { hide() }
|
|
||||||
.collect(resultCollector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun awaitResult(): ConfirmResult {
|
|
||||||
return suspendCancellableCoroutine {
|
|
||||||
awaitContinuation = it.apply {
|
|
||||||
if (isCallbackEmpty) {
|
|
||||||
invokeOnCancellation {
|
|
||||||
visible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateVisuals(visuals: ConfirmDialogVisuals) {
|
|
||||||
this.visuals = visuals
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun show() {
|
|
||||||
if (visuals !== ConfirmDialogVisualsImpl.Empty) {
|
|
||||||
super.show()
|
|
||||||
} else {
|
|
||||||
throw UnsupportedOperationException("can't show confirm dialog with the Empty visuals")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showConfirm(
|
|
||||||
title: String,
|
|
||||||
content: String,
|
|
||||||
markdown: Boolean,
|
|
||||||
confirm: String?,
|
|
||||||
dismiss: String?
|
|
||||||
) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun awaitConfirm(
|
|
||||||
title: String,
|
|
||||||
content: String,
|
|
||||||
markdown: Boolean,
|
|
||||||
confirm: String?,
|
|
||||||
dismiss: String?
|
|
||||||
): ConfirmResult {
|
|
||||||
coroutineScope.launch {
|
|
||||||
updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
return awaitResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val dialogType: String get() = "ConfirmDialog"
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "${super.toString()}(visuals: $visuals)"
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun Saver(
|
|
||||||
visible: MutableState<Boolean>,
|
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
callback: ConfirmCallback,
|
|
||||||
resultChannel: ReceiveChannel<ConfirmResult>
|
|
||||||
) = Saver<ConfirmDialogHandle, ConfirmDialogVisuals>(
|
|
||||||
save = {
|
|
||||||
it.visuals
|
|
||||||
},
|
|
||||||
restore = {
|
|
||||||
Log.d(TAG, "ConfirmDialog restore, visuals: $it")
|
|
||||||
ConfirmDialogHandleImpl(visible, coroutineScope, callback, it, resultChannel)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CustomDialogHandleImpl(
|
|
||||||
visible: MutableState<Boolean>,
|
|
||||||
coroutineScope: CoroutineScope
|
|
||||||
) : DialogHandleBase(visible, coroutineScope) {
|
|
||||||
override val dialogType: String get() = "CustomDialog"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberLoadingDialog(): LoadingDialogHandle {
|
|
||||||
val visible = remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
if (visible.value) {
|
|
||||||
LoadingDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
return remember {
|
|
||||||
LoadingDialogHandleImpl(visible, coroutineScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun rememberConfirmDialog(visuals: ConfirmDialogVisuals, callback: ConfirmCallback): ConfirmDialogHandle {
|
|
||||||
val visible = rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val resultChannel = remember {
|
|
||||||
Channel<ConfirmResult>()
|
|
||||||
}
|
|
||||||
|
|
||||||
val handle = rememberSaveable(
|
|
||||||
saver = ConfirmDialogHandleImpl.Saver(visible, coroutineScope, callback, resultChannel),
|
|
||||||
init = {
|
|
||||||
ConfirmDialogHandleImpl(visible, coroutineScope, callback, visuals, resultChannel)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (visible.value) {
|
|
||||||
ConfirmDialog(
|
|
||||||
handle.visuals,
|
|
||||||
confirm = { coroutineScope.launch { resultChannel.send(ConfirmResult.Confirmed) } },
|
|
||||||
dismiss = { coroutineScope.launch { resultChannel.send(ConfirmResult.Canceled) } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return handle
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberConfirmCallback(onConfirm: NullableCallback, onDismiss: NullableCallback): ConfirmCallback {
|
|
||||||
val currentOnConfirm by rememberUpdatedState(newValue = onConfirm)
|
|
||||||
val currentOnDismiss by rememberUpdatedState(newValue = onDismiss)
|
|
||||||
return remember {
|
|
||||||
ConfirmCallback({ currentOnConfirm }, { currentOnDismiss })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberConfirmDialog(onConfirm: NullableCallback = null, onDismiss: NullableCallback = null): ConfirmDialogHandle {
|
|
||||||
return rememberConfirmDialog(rememberConfirmCallback(onConfirm, onDismiss))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberConfirmDialog(callback: ConfirmCallback): ConfirmDialogHandle {
|
|
||||||
return rememberConfirmDialog(ConfirmDialogVisualsImpl.Empty, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberCustomDialog(composable: @Composable (dismiss: () -> Unit) -> Unit): DialogHandle {
|
|
||||||
val visible = rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
if (visible.value) {
|
|
||||||
composable { visible.value = false }
|
|
||||||
}
|
|
||||||
return remember {
|
|
||||||
CustomDialogHandleImpl(visible, coroutineScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun LoadingDialog() {
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = {},
|
|
||||||
properties = DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
|
|
||||||
) {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.size(100.dp), shape = RoundedCornerShape(8.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
CircularProgressIndicator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, dismiss: () -> Unit) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = {
|
|
||||||
dismiss()
|
|
||||||
},
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = visuals.title,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
if (visuals.isMarkdown) {
|
|
||||||
MarkdownContent(content = visuals.content)
|
|
||||||
} else {
|
|
||||||
Text(text = visuals.content)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = confirm) {
|
|
||||||
Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = dismiss) {
|
|
||||||
Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun MarkdownContent(content: String) {
|
|
||||||
val contentColor = LocalContentColor.current
|
|
||||||
|
|
||||||
AndroidView(
|
|
||||||
factory = { context ->
|
|
||||||
TextView(context).apply {
|
|
||||||
movementMethod = LinkMovementMethod.getInstance()
|
|
||||||
setSpannableFactory(NoCopySpannableFactory.getInstance())
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
breakStrategy = LineBreaker.BREAK_STRATEGY_SIMPLE
|
|
||||||
}
|
|
||||||
hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE
|
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight(),
|
|
||||||
update = {
|
|
||||||
Markwon.create(it.context).setMarkdown(it, content)
|
|
||||||
it.setTextColor(contentColor.toArgb())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.component
|
|
||||||
|
|
||||||
import androidx.compose.foundation.focusable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.input.key.KeyEvent
|
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun KeyEventBlocker(predicate: (KeyEvent) -> Boolean) {
|
|
||||||
val requester = remember { FocusRequester() }
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.onKeyEvent {
|
|
||||||
predicate(it)
|
|
||||||
}
|
|
||||||
.focusRequester(requester)
|
|
||||||
.focusable()
|
|
||||||
)
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
requester.requestFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.component
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.only
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material.icons.filled.Search
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
|
|
||||||
private const val TAG = "SearchBar"
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun SearchAppBar(
|
|
||||||
title: @Composable () -> Unit,
|
|
||||||
searchText: String,
|
|
||||||
onSearchTextChange: (String) -> Unit,
|
|
||||||
onClearClick: () -> Unit,
|
|
||||||
onBackClick: (() -> Unit)? = null,
|
|
||||||
onConfirm: (() -> Unit)? = null,
|
|
||||||
dropdownContent: @Composable (() -> Unit)? = null,
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
|
||||||
var onSearch by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (onSearch) {
|
|
||||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
|
||||||
}
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
onDispose {
|
|
||||||
keyboardController?.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Box {
|
|
||||||
AnimatedVisibility(
|
|
||||||
modifier = Modifier.align(Alignment.CenterStart),
|
|
||||||
visible = !onSearch,
|
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut(),
|
|
||||||
content = { title() }
|
|
||||||
)
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = onSearch,
|
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut()
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 2.dp, bottom = 2.dp, end = if (onBackClick != null) 0.dp else 14.dp)
|
|
||||||
.focusRequester(focusRequester)
|
|
||||||
.onFocusChanged { focusState ->
|
|
||||||
if (focusState.isFocused) onSearch = true
|
|
||||||
Log.d(TAG, "onFocusChanged: $focusState")
|
|
||||||
},
|
|
||||||
value = searchText,
|
|
||||||
onValueChange = onSearchTextChange,
|
|
||||||
trailingIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
onSearch = false
|
|
||||||
keyboardController?.hide()
|
|
||||||
onClearClick()
|
|
||||||
},
|
|
||||||
content = { Icon(Icons.Filled.Close, null) }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
maxLines = 1,
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
|
||||||
keyboardActions = KeyboardActions(onDone = {
|
|
||||||
keyboardController?.hide()
|
|
||||||
onConfirm?.invoke()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
if (onBackClick != null) {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBackClick,
|
|
||||||
content = { Icon(Icons.AutoMirrored.Outlined.ArrowBack, null) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = !onSearch
|
|
||||||
) {
|
|
||||||
IconButton(
|
|
||||||
onClick = { onSearch = true },
|
|
||||||
content = { Icon(Icons.Filled.Search, null) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dropdownContent != null) {
|
|
||||||
dropdownContent()
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun SearchAppBarPreview() {
|
|
||||||
var searchText by remember { mutableStateOf("") }
|
|
||||||
SearchAppBar(
|
|
||||||
title = { Text("Search text") },
|
|
||||||
searchText = searchText,
|
|
||||||
onSearchTextChange = { searchText = it },
|
|
||||||
onClearClick = { searchText = "" }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.component
|
|
||||||
|
|
||||||
import androidx.compose.foundation.LocalIndication
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.selection.toggleable
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.LocalTextStyle
|
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.semantics.Role
|
|
||||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
|
||||||
import com.dergoogler.mmrl.ui.component.text.TextRow
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SwitchItem(
|
|
||||||
icon: ImageVector? = null,
|
|
||||||
title: String,
|
|
||||||
summary: String? = null,
|
|
||||||
checked: Boolean,
|
|
||||||
enabled: Boolean = true,
|
|
||||||
beta: Boolean = false,
|
|
||||||
onCheckedChange: (Boolean) -> Unit,
|
|
||||||
) {
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
val stateAlpha = remember(checked, enabled) { Modifier.alpha(if (enabled) 1f else 0.5f) }
|
|
||||||
|
|
||||||
ListItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.toggleable(
|
|
||||||
value = checked,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
role = Role.Switch,
|
|
||||||
enabled = enabled,
|
|
||||||
indication = LocalIndication.current,
|
|
||||||
onValueChange = onCheckedChange
|
|
||||||
),
|
|
||||||
headlineContent = {
|
|
||||||
TextRow(
|
|
||||||
leadingContent = if (beta) {
|
|
||||||
{
|
|
||||||
LabelItem(
|
|
||||||
modifier = Modifier.then(stateAlpha),
|
|
||||||
text = "Beta"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else null
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.then(stateAlpha),
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leadingContent = icon?.let {
|
|
||||||
{
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier.then(stateAlpha),
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = title
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
trailingContent = {
|
|
||||||
Switch(
|
|
||||||
checked = checked,
|
|
||||||
enabled = enabled,
|
|
||||||
onCheckedChange = onCheckedChange,
|
|
||||||
interactionSource = interactionSource
|
|
||||||
)
|
|
||||||
},
|
|
||||||
supportingContent = {
|
|
||||||
if (summary != null) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.then(stateAlpha),
|
|
||||||
text = summary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RadioItem(
|
|
||||||
title: String,
|
|
||||||
selected: Boolean,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
) {
|
|
||||||
ListItem(
|
|
||||||
headlineContent = {
|
|
||||||
Text(title)
|
|
||||||
},
|
|
||||||
leadingContent = {
|
|
||||||
RadioButton(selected = selected, onClick = onClick)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.component.profile
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.SwitchItem
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AppProfileConfig(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
fixedName: Boolean,
|
|
||||||
enabled: Boolean,
|
|
||||||
profile: Natives.Profile,
|
|
||||||
onProfileChange: (Natives.Profile) -> Unit,
|
|
||||||
) {
|
|
||||||
Column(modifier = modifier) {
|
|
||||||
if (!fixedName) {
|
|
||||||
OutlinedTextField(
|
|
||||||
label = { Text(stringResource(R.string.profile_name)) },
|
|
||||||
value = profile.name,
|
|
||||||
onValueChange = { onProfileChange(profile.copy(name = it)) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitchItem(
|
|
||||||
title = stringResource(R.string.profile_umount_modules),
|
|
||||||
summary = stringResource(R.string.profile_umount_modules_summary),
|
|
||||||
checked = if (enabled) {
|
|
||||||
profile.umountModules
|
|
||||||
} else {
|
|
||||||
Natives.isDefaultUmountModules()
|
|
||||||
},
|
|
||||||
enabled = enabled,
|
|
||||||
onCheckedChange = {
|
|
||||||
onProfileChange(
|
|
||||||
profile.copy(
|
|
||||||
umountModules = it,
|
|
||||||
nonRootUseDefault = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun AppProfileConfigPreview() {
|
|
||||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
|
||||||
AppProfileConfig(fixedName = true, enabled = false, profile = profile) {
|
|
||||||
profile = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-487
@@ -1,487 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.component.profile
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
|
||||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
|
||||||
import androidx.compose.material3.AssistChip
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.MenuAnchorType
|
|
||||||
import androidx.compose.material3.OutlinedCard
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.text.isDigitsOnly
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.Header
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
|
||||||
import com.maxkeppeler.sheets.input.InputDialog
|
|
||||||
import com.maxkeppeler.sheets.input.models.InputHeader
|
|
||||||
import com.maxkeppeler.sheets.input.models.InputSelection
|
|
||||||
import com.maxkeppeler.sheets.input.models.InputTextField
|
|
||||||
import com.maxkeppeler.sheets.input.models.InputTextFieldType
|
|
||||||
import com.maxkeppeler.sheets.input.models.ValidationResult
|
|
||||||
import com.maxkeppeler.sheets.list.ListDialog
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListOption
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.profile.Capabilities
|
|
||||||
import com.rifsxd.ksunext.profile.Groups
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberCustomDialog
|
|
||||||
import com.rifsxd.ksunext.ui.util.isSepolicyValid
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun RootProfileConfig(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
fixedName: Boolean,
|
|
||||||
profile: Natives.Profile,
|
|
||||||
onProfileChange: (Natives.Profile) -> Unit,
|
|
||||||
) {
|
|
||||||
Column(modifier = modifier) {
|
|
||||||
if (!fixedName) {
|
|
||||||
OutlinedTextField(
|
|
||||||
label = { Text(stringResource(R.string.profile_name)) },
|
|
||||||
value = profile.name,
|
|
||||||
onValueChange = { onProfileChange(profile.copy(name = it)) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
|
||||||
val currentNamespace = when (profile.namespace) {
|
|
||||||
Natives.Profile.Namespace.INHERITED.ordinal -> stringResource(R.string.profile_namespace_inherited)
|
|
||||||
Natives.Profile.Namespace.GLOBAL.ordinal -> stringResource(R.string.profile_namespace_global)
|
|
||||||
Natives.Profile.Namespace.INDIVIDUAL.ordinal -> stringResource(R.string.profile_namespace_individual)
|
|
||||||
else -> stringResource(R.string.profile_namespace_inherited)
|
|
||||||
}
|
|
||||||
ListItem(headlineContent = {
|
|
||||||
ExposedDropdownMenuBox(
|
|
||||||
expanded = expanded,
|
|
||||||
onExpandedChange = { expanded = !expanded }
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
modifier = Modifier
|
|
||||||
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
readOnly = true,
|
|
||||||
label = { Text(stringResource(R.string.profile_namespace)) },
|
|
||||||
value = currentNamespace,
|
|
||||||
onValueChange = {},
|
|
||||||
trailingIcon = {
|
|
||||||
if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
|
|
||||||
else Icon(Icons.Filled.ArrowDropDown, null)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
ExposedDropdownMenu(
|
|
||||||
expanded = expanded,
|
|
||||||
onDismissRequest = { expanded = false }
|
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.profile_namespace_inherited)) },
|
|
||||||
onClick = {
|
|
||||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.INHERITED.ordinal))
|
|
||||||
expanded = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.profile_namespace_global)) },
|
|
||||||
onClick = {
|
|
||||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.GLOBAL.ordinal))
|
|
||||||
expanded = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.profile_namespace_individual)) },
|
|
||||||
onClick = {
|
|
||||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.INDIVIDUAL.ordinal))
|
|
||||||
expanded = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
UidPanel(uid = profile.uid, label = "uid", onUidChange = {
|
|
||||||
onProfileChange(
|
|
||||||
profile.copy(
|
|
||||||
uid = it,
|
|
||||||
rootUseDefault = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
UidPanel(uid = profile.gid, label = "gid", onUidChange = {
|
|
||||||
onProfileChange(
|
|
||||||
profile.copy(
|
|
||||||
gid = it,
|
|
||||||
rootUseDefault = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
val selectedGroups = profile.groups.ifEmpty { listOf(0) }.let { e ->
|
|
||||||
e.mapNotNull { g ->
|
|
||||||
Groups.entries.find { it.gid == g }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GroupsPanel(selectedGroups) {
|
|
||||||
onProfileChange(
|
|
||||||
profile.copy(
|
|
||||||
groups = it.map { group -> group.gid }.ifEmpty { listOf(0) },
|
|
||||||
rootUseDefault = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val selectedCaps = profile.capabilities.mapNotNull { e ->
|
|
||||||
Capabilities.entries.find { it.cap == e }
|
|
||||||
}
|
|
||||||
|
|
||||||
CapsPanel(selectedCaps) {
|
|
||||||
onProfileChange(
|
|
||||||
profile.copy(
|
|
||||||
capabilities = it.map { cap -> cap.cap },
|
|
||||||
rootUseDefault = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SELinuxPanel(profile = profile, onSELinuxChange = { domain, rules ->
|
|
||||||
onProfileChange(
|
|
||||||
profile.copy(
|
|
||||||
context = domain,
|
|
||||||
rules = rules,
|
|
||||||
rootUseDefault = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>) -> Unit) {
|
|
||||||
val selectGroupsDialog = rememberCustomDialog { dismiss: () -> Unit ->
|
|
||||||
val groups = Groups.entries.toTypedArray().sortedWith(
|
|
||||||
compareBy<Groups> { if (selected.contains(it)) 0 else 1 }
|
|
||||||
.then(compareBy {
|
|
||||||
when (it) {
|
|
||||||
Groups.ROOT -> 0
|
|
||||||
Groups.SYSTEM -> 1
|
|
||||||
Groups.SHELL -> 2
|
|
||||||
else -> Int.MAX_VALUE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(compareBy { it.name })
|
|
||||||
|
|
||||||
)
|
|
||||||
val options = groups.map { value ->
|
|
||||||
ListOption(
|
|
||||||
titleText = value.display,
|
|
||||||
subtitleText = value.desc,
|
|
||||||
selected = selected.contains(value),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val selection = HashSet(selected)
|
|
||||||
ListDialog(
|
|
||||||
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
|
||||||
closeSelection(selection)
|
|
||||||
}, onCloseRequest = {
|
|
||||||
dismiss()
|
|
||||||
}),
|
|
||||||
header = Header.Default(
|
|
||||||
title = stringResource(R.string.profile_groups),
|
|
||||||
),
|
|
||||||
selection = ListSelection.Multiple(
|
|
||||||
showCheckBoxes = true,
|
|
||||||
options = options,
|
|
||||||
maxChoices = 32, // Kernel only supports 32 groups at most
|
|
||||||
) { indecies, _ ->
|
|
||||||
// Handle selection
|
|
||||||
selection.clear()
|
|
||||||
indecies.forEach { index ->
|
|
||||||
val group = groups[index]
|
|
||||||
selection.add(group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedCard(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.clickable {
|
|
||||||
selectGroupsDialog.show()
|
|
||||||
}
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.profile_groups))
|
|
||||||
FlowRow {
|
|
||||||
selected.forEach { group ->
|
|
||||||
AssistChip(
|
|
||||||
modifier = Modifier.padding(3.dp),
|
|
||||||
onClick = { /*TODO*/ },
|
|
||||||
label = { Text(group.display) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun CapsPanel(
|
|
||||||
selected: Collection<Capabilities>,
|
|
||||||
closeSelection: (selection: Set<Capabilities>) -> Unit
|
|
||||||
) {
|
|
||||||
val selectCapabilitiesDialog = rememberCustomDialog { dismiss ->
|
|
||||||
val caps = Capabilities.entries.toTypedArray().sortedWith(
|
|
||||||
compareBy<Capabilities> { if (selected.contains(it)) 0 else 1 }
|
|
||||||
.then(compareBy { it.name })
|
|
||||||
)
|
|
||||||
val options = caps.map { value ->
|
|
||||||
ListOption(
|
|
||||||
titleText = value.display,
|
|
||||||
subtitleText = value.desc,
|
|
||||||
selected = selected.contains(value),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val selection = HashSet(selected)
|
|
||||||
ListDialog(
|
|
||||||
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
|
||||||
closeSelection(selection)
|
|
||||||
}, onCloseRequest = {
|
|
||||||
dismiss()
|
|
||||||
}),
|
|
||||||
header = Header.Default(
|
|
||||||
title = stringResource(R.string.profile_capabilities),
|
|
||||||
),
|
|
||||||
selection = ListSelection.Multiple(
|
|
||||||
showCheckBoxes = true,
|
|
||||||
options = options
|
|
||||||
) { indecies, _ ->
|
|
||||||
// Handle selection
|
|
||||||
selection.clear()
|
|
||||||
indecies.forEach { index ->
|
|
||||||
val group = caps[index]
|
|
||||||
selection.add(group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedCard(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.clickable {
|
|
||||||
selectCapabilitiesDialog.show()
|
|
||||||
}
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.profile_capabilities))
|
|
||||||
FlowRow {
|
|
||||||
selected.forEach { group ->
|
|
||||||
AssistChip(
|
|
||||||
modifier = Modifier.padding(3.dp),
|
|
||||||
onClick = { /*TODO*/ },
|
|
||||||
label = { Text(group.display) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun UidPanel(uid: Int, label: String, onUidChange: (Int) -> Unit) {
|
|
||||||
|
|
||||||
ListItem(headlineContent = {
|
|
||||||
var isError by remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
var lastValidUid by remember {
|
|
||||||
mutableIntStateOf(uid)
|
|
||||||
}
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
label = { Text(label) },
|
|
||||||
value = uid.toString(),
|
|
||||||
isError = isError,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Number,
|
|
||||||
imeAction = ImeAction.Done
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(onDone = {
|
|
||||||
keyboardController?.hide()
|
|
||||||
}),
|
|
||||||
onValueChange = {
|
|
||||||
if (it.isEmpty()) {
|
|
||||||
onUidChange(0)
|
|
||||||
return@OutlinedTextField
|
|
||||||
}
|
|
||||||
val valid = isTextValidUid(it)
|
|
||||||
|
|
||||||
val targetUid = if (valid) it.toInt() else lastValidUid
|
|
||||||
if (valid) {
|
|
||||||
lastValidUid = it.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
onUidChange(targetUid)
|
|
||||||
|
|
||||||
isError = !valid
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun SELinuxPanel(
|
|
||||||
profile: Natives.Profile,
|
|
||||||
onSELinuxChange: (domain: String, rules: String) -> Unit
|
|
||||||
) {
|
|
||||||
val editSELinuxDialog = rememberCustomDialog { dismiss ->
|
|
||||||
var domain by remember { mutableStateOf(profile.context) }
|
|
||||||
var rules by remember { mutableStateOf(profile.rules) }
|
|
||||||
|
|
||||||
val inputOptions = listOf(
|
|
||||||
InputTextField(
|
|
||||||
text = domain,
|
|
||||||
header = InputHeader(
|
|
||||||
title = stringResource(id = R.string.profile_selinux_domain),
|
|
||||||
),
|
|
||||||
type = InputTextFieldType.OUTLINED,
|
|
||||||
required = true,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Ascii,
|
|
||||||
imeAction = ImeAction.Next
|
|
||||||
),
|
|
||||||
resultListener = {
|
|
||||||
domain = it ?: ""
|
|
||||||
},
|
|
||||||
validationListener = { value ->
|
|
||||||
// value can be a-zA-Z0-9_
|
|
||||||
val regex = Regex("^[a-z_]+:[a-z0-9_]+:[a-z0-9_]+(:[a-z0-9_]+)?$")
|
|
||||||
if (value?.matches(regex) == true) ValidationResult.Valid
|
|
||||||
else ValidationResult.Invalid("Domain must be in the format of \"user:role:type:level\"")
|
|
||||||
}
|
|
||||||
),
|
|
||||||
InputTextField(
|
|
||||||
text = rules,
|
|
||||||
header = InputHeader(
|
|
||||||
title = stringResource(id = R.string.profile_selinux_rules),
|
|
||||||
),
|
|
||||||
type = InputTextFieldType.OUTLINED,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Ascii,
|
|
||||||
),
|
|
||||||
singleLine = false,
|
|
||||||
resultListener = {
|
|
||||||
rules = it ?: ""
|
|
||||||
},
|
|
||||||
validationListener = { value ->
|
|
||||||
if (isSepolicyValid(value)) ValidationResult.Valid
|
|
||||||
else ValidationResult.Invalid("SELinux rules is invalid!")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
InputDialog(
|
|
||||||
state = rememberUseCaseState(visible = true,
|
|
||||||
onFinishedRequest = {
|
|
||||||
onSELinuxChange(domain, rules)
|
|
||||||
},
|
|
||||||
onCloseRequest = {
|
|
||||||
dismiss()
|
|
||||||
}),
|
|
||||||
header = Header.Default(
|
|
||||||
title = stringResource(R.string.profile_selinux_context),
|
|
||||||
),
|
|
||||||
selection = InputSelection(
|
|
||||||
input = inputOptions,
|
|
||||||
onPositiveClick = { result ->
|
|
||||||
// Handle selection
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ListItem(headlineContent = {
|
|
||||||
OutlinedTextField(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
editSELinuxDialog.show()
|
|
||||||
},
|
|
||||||
enabled = false,
|
|
||||||
colors = OutlinedTextFieldDefaults.colors(
|
|
||||||
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
|
||||||
disabledBorderColor = MaterialTheme.colorScheme.outline,
|
|
||||||
disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
label = { Text(text = stringResource(R.string.profile_selinux_context)) },
|
|
||||||
value = profile.context,
|
|
||||||
onValueChange = { }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun RootProfileConfigPreview() {
|
|
||||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
|
||||||
RootProfileConfig(fixedName = true, profile = profile) {
|
|
||||||
profile = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isTextValidUid(text: String): Boolean {
|
|
||||||
return text.isNotEmpty() && text.isDigitsOnly() && text.toInt() >= 0 && text.toInt() <= Int.MAX_VALUE
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.component.profile
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ReadMore
|
|
||||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
|
||||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
|
||||||
import androidx.compose.material.icons.filled.Create
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.MenuAnchorType
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.util.listAppProfileTemplates
|
|
||||||
import com.rifsxd.ksunext.ui.util.setSepolicy
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.getTemplateInfoById
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/10/21.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun TemplateConfig(
|
|
||||||
profile: Natives.Profile,
|
|
||||||
onViewTemplate: (id: String) -> Unit = {},
|
|
||||||
onManageTemplate: () -> Unit = {},
|
|
||||||
onProfileChange: (Natives.Profile) -> Unit
|
|
||||||
) {
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
|
||||||
var template by rememberSaveable {
|
|
||||||
mutableStateOf(profile.rootTemplate ?: "")
|
|
||||||
}
|
|
||||||
val profileTemplates = listAppProfileTemplates()
|
|
||||||
val noTemplates = profileTemplates.isEmpty()
|
|
||||||
|
|
||||||
ListItem(headlineContent = {
|
|
||||||
ExposedDropdownMenuBox(
|
|
||||||
expanded = expanded,
|
|
||||||
onExpandedChange = { expanded = it },
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
modifier = Modifier
|
|
||||||
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
readOnly = true,
|
|
||||||
label = { Text(stringResource(R.string.profile_template)) },
|
|
||||||
value = template.ifEmpty { "None" },
|
|
||||||
onValueChange = {},
|
|
||||||
trailingIcon = {
|
|
||||||
if (noTemplates) {
|
|
||||||
IconButton(
|
|
||||||
onClick = onManageTemplate
|
|
||||||
) {
|
|
||||||
Icon(Icons.Filled.Create, null)
|
|
||||||
}
|
|
||||||
} else if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
|
|
||||||
else Icon(Icons.Filled.ArrowDropDown, null)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (profileTemplates.isEmpty()) {
|
|
||||||
return@ExposedDropdownMenuBox
|
|
||||||
}
|
|
||||||
ExposedDropdownMenu(
|
|
||||||
expanded = expanded,
|
|
||||||
onDismissRequest = { expanded = false }
|
|
||||||
) {
|
|
||||||
profileTemplates.forEach { tid ->
|
|
||||||
val templateInfo =
|
|
||||||
getTemplateInfoById(tid) ?: return@forEach
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(tid) },
|
|
||||||
onClick = {
|
|
||||||
template = tid
|
|
||||||
if (setSepolicy(tid, templateInfo.rules.joinToString("\n"))) {
|
|
||||||
onProfileChange(
|
|
||||||
profile.copy(
|
|
||||||
rootTemplate = tid,
|
|
||||||
rootUseDefault = false,
|
|
||||||
uid = templateInfo.uid,
|
|
||||||
gid = templateInfo.gid,
|
|
||||||
groups = templateInfo.groups,
|
|
||||||
capabilities = templateInfo.capabilities,
|
|
||||||
context = templateInfo.context,
|
|
||||||
namespace = templateInfo.namespace,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
expanded = false
|
|
||||||
},
|
|
||||||
trailingIcon = {
|
|
||||||
IconButton(onClick = {
|
|
||||||
onViewTemplate(tid)
|
|
||||||
}) {
|
|
||||||
Icon(Icons.AutoMirrored.Filled.ReadMore, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,410 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.animation.Crossfade
|
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
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.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.only
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.AccountCircle
|
|
||||||
import androidx.compose.material.icons.filled.Android
|
|
||||||
import androidx.compose.material.icons.filled.AdminPanelSettings
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.FilterChip
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
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.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.DpOffset
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.SwitchItem
|
|
||||||
import com.rifsxd.ksunext.ui.component.profile.AppProfileConfig
|
|
||||||
import com.rifsxd.ksunext.ui.component.profile.RootProfileConfig
|
|
||||||
import com.rifsxd.ksunext.ui.component.profile.TemplateConfig
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|
||||||
import com.rifsxd.ksunext.ui.util.forceStopApp
|
|
||||||
import com.rifsxd.ksunext.ui.util.getSepolicy
|
|
||||||
import com.rifsxd.ksunext.ui.util.launchApp
|
|
||||||
import com.rifsxd.ksunext.ui.util.restartApp
|
|
||||||
import com.rifsxd.ksunext.ui.util.setSepolicy
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.SuperUserViewModel
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.getTemplateInfoById
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/5/16.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun AppProfileScreen(
|
|
||||||
navigator: DestinationsNavigator,
|
|
||||||
appInfo: SuperUserViewModel.AppInfo,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val viewModel: SuperUserViewModel = viewModel()
|
|
||||||
val failToUpdateAppProfile = stringResource(R.string.failed_to_update_app_profile).format(appInfo.label)
|
|
||||||
val failToUpdateSepolicy = stringResource(R.string.failed_to_update_sepolicy).format(appInfo.label)
|
|
||||||
val suNotAllowed = stringResource(R.string.su_not_allowed).format(appInfo.label)
|
|
||||||
|
|
||||||
val packageName = appInfo.packageName
|
|
||||||
val initialProfile = Natives.getAppProfile(packageName, appInfo.uid)
|
|
||||||
if (initialProfile.allowSu) {
|
|
||||||
initialProfile.rules = getSepolicy(packageName)
|
|
||||||
}
|
|
||||||
var profile by rememberSaveable {
|
|
||||||
mutableStateOf(initialProfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
onBack = dropUnlessResumed { navigator.popBackStack() },
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
snackbarHost = { SnackbarHost(hostState = snackBarHost) },
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { paddingValues ->
|
|
||||||
AppProfileInner(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(paddingValues)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
packageName = appInfo.packageName,
|
|
||||||
appLabel = appInfo.label,
|
|
||||||
appIcon = {
|
|
||||||
AsyncImage(
|
|
||||||
model = ImageRequest.Builder(context).data(appInfo.packageInfo).crossfade(true).build(),
|
|
||||||
contentDescription = appInfo.label,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.width(48.dp)
|
|
||||||
.height(48.dp)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
profile = profile,
|
|
||||||
onViewTemplate = {
|
|
||||||
getTemplateInfoById(it)?.let { info ->
|
|
||||||
navigator.navigate(TemplateEditorScreenDestination(info))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onManageTemplate = {
|
|
||||||
navigator.navigate(AppProfileTemplateScreenDestination())
|
|
||||||
},
|
|
||||||
onProfileChange = {
|
|
||||||
scope.launch {
|
|
||||||
if (it.allowSu) {
|
|
||||||
// sync with allowlist.c - forbid_system_uid
|
|
||||||
if (appInfo.uid < 2000 && appInfo.uid != 1000) {
|
|
||||||
snackBarHost.showSnackbar(suNotAllowed)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
if (!it.rootUseDefault && it.rules.isNotEmpty() && !setSepolicy(profile.name, it.rules)) {
|
|
||||||
snackBarHost.showSnackbar(failToUpdateSepolicy)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!Natives.setAppProfile(it)) {
|
|
||||||
snackBarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
|
|
||||||
} else {
|
|
||||||
profile = it
|
|
||||||
viewModel.updateAppProfile(packageName, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AppProfileInner(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
packageName: String,
|
|
||||||
appLabel: String,
|
|
||||||
appIcon: @Composable () -> Unit,
|
|
||||||
profile: Natives.Profile,
|
|
||||||
onViewTemplate: (id: String) -> Unit = {},
|
|
||||||
onManageTemplate: () -> Unit = {},
|
|
||||||
onProfileChange: (Natives.Profile) -> Unit,
|
|
||||||
) {
|
|
||||||
val isRootGranted = profile.allowSu
|
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
|
||||||
AppMenuBox(packageName) {
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = appLabel,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
supportingContent = { Text(packageName) },
|
|
||||||
leadingContent = appIcon,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.AdminPanelSettings,
|
|
||||||
title = stringResource(id = R.string.superuser),
|
|
||||||
checked = isRootGranted,
|
|
||||||
onCheckedChange = { onProfileChange(profile.copy(allowSu = it)) },
|
|
||||||
)
|
|
||||||
|
|
||||||
Crossfade(targetState = isRootGranted, label = "") { current ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(bottom = 6.dp + 48.dp + 6.dp /* SnackBar height */)
|
|
||||||
) {
|
|
||||||
if (current) {
|
|
||||||
val initialMode = if (profile.rootUseDefault) {
|
|
||||||
Mode.Default
|
|
||||||
} else if (profile.rootTemplate != null) {
|
|
||||||
Mode.Template
|
|
||||||
} else {
|
|
||||||
Mode.Custom
|
|
||||||
}
|
|
||||||
var mode by rememberSaveable {
|
|
||||||
mutableStateOf(initialMode)
|
|
||||||
}
|
|
||||||
ProfileBox(mode, true) {
|
|
||||||
// template mode shouldn't change profile here!
|
|
||||||
if (it == Mode.Default || it == Mode.Custom) {
|
|
||||||
onProfileChange(profile.copy(rootUseDefault = it == Mode.Default))
|
|
||||||
}
|
|
||||||
mode = it
|
|
||||||
}
|
|
||||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
|
||||||
if (currentMode == Mode.Template) {
|
|
||||||
TemplateConfig(
|
|
||||||
profile = profile,
|
|
||||||
onViewTemplate = onViewTemplate,
|
|
||||||
onManageTemplate = onManageTemplate,
|
|
||||||
onProfileChange = onProfileChange
|
|
||||||
)
|
|
||||||
} else if (mode == Mode.Custom) {
|
|
||||||
RootProfileConfig(
|
|
||||||
fixedName = true,
|
|
||||||
profile = profile,
|
|
||||||
onProfileChange = onProfileChange
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val mode = if (profile.nonRootUseDefault) Mode.Default else Mode.Custom
|
|
||||||
ProfileBox(mode, false) {
|
|
||||||
onProfileChange(profile.copy(nonRootUseDefault = (it == Mode.Default)))
|
|
||||||
}
|
|
||||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
|
||||||
val modifyEnabled = currentMode == Mode.Custom
|
|
||||||
AppProfileConfig(
|
|
||||||
fixedName = true,
|
|
||||||
profile = profile,
|
|
||||||
enabled = modifyEnabled,
|
|
||||||
onProfileChange = onProfileChange
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum class Mode(@StringRes private val res: Int) {
|
|
||||||
Default(R.string.profile_default), Template(R.string.profile_template), Custom(R.string.profile_custom);
|
|
||||||
|
|
||||||
val text: String
|
|
||||||
@Composable get() = stringResource(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
onBack: () -> Unit,
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.profile),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ProfileBox(
|
|
||||||
mode: Mode,
|
|
||||||
hasTemplate: Boolean,
|
|
||||||
onModeChange: (Mode) -> Unit,
|
|
||||||
) {
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = stringResource(R.string.profile),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
supportingContent = { Text(mode.text) },
|
|
||||||
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
|
|
||||||
)
|
|
||||||
HorizontalDivider(thickness = Dp.Hairline)
|
|
||||||
ListItem(headlineContent = {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
|
||||||
FilterChip(
|
|
||||||
selected = mode == Mode.Default,
|
|
||||||
label = { Text(stringResource(R.string.profile_default)) },
|
|
||||||
onClick = { onModeChange(Mode.Default) },
|
|
||||||
)
|
|
||||||
if (hasTemplate) {
|
|
||||||
FilterChip(
|
|
||||||
selected = mode == Mode.Template,
|
|
||||||
label = { Text(stringResource(R.string.profile_template)) },
|
|
||||||
onClick = { onModeChange(Mode.Template) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
FilterChip(
|
|
||||||
selected = mode == Mode.Custom,
|
|
||||||
label = { Text(stringResource(R.string.profile_custom)) },
|
|
||||||
onClick = { onModeChange(Mode.Custom) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
|
|
||||||
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
|
||||||
var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) }
|
|
||||||
val density = LocalDensity.current
|
|
||||||
|
|
||||||
BoxWithConstraints(
|
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.pointerInput(Unit) {
|
|
||||||
detectTapGestures {
|
|
||||||
touchPoint = it
|
|
||||||
expanded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
|
|
||||||
content()
|
|
||||||
|
|
||||||
val (offsetX, offsetY) = with(density) {
|
|
||||||
(touchPoint.x.toDp()) to (touchPoint.y.toDp())
|
|
||||||
}
|
|
||||||
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = expanded,
|
|
||||||
offset = DpOffset(offsetX, -offsetY),
|
|
||||||
onDismissRequest = {
|
|
||||||
expanded = false
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(id = R.string.launch_app)) },
|
|
||||||
onClick = {
|
|
||||||
expanded = false
|
|
||||||
launchApp(packageName)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(id = R.string.force_stop_app)) },
|
|
||||||
onClick = {
|
|
||||||
expanded = false
|
|
||||||
forceStopApp(packageName)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(id = R.string.restart_app)) },
|
|
||||||
onClick = {
|
|
||||||
expanded = false
|
|
||||||
restartApp(packageName)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun AppProfilePreview() {
|
|
||||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
|
||||||
AppProfileInner(
|
|
||||||
packageName = "icu.nullptr.test",
|
|
||||||
appLabel = "Test",
|
|
||||||
appIcon = { Icon(Icons.Filled.Android, null) },
|
|
||||||
profile = profile,
|
|
||||||
onProfileChange = {
|
|
||||||
profile = it
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
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.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.*
|
|
||||||
import androidx.compose.material.icons.filled.*
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.ksuApp
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.ConfirmResult
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberLoadingDialog
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|
||||||
import com.rifsxd.ksunext.ui.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author rifsxd
|
|
||||||
* @date 2025/1/14.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun BackupRestoreScreen(navigator: DestinationsNavigator) {
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
onBack = dropUnlessResumed {
|
|
||||||
navigator.popBackStack()
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
snackbarHost = { SnackbarHost(snackBarHost) },
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { paddingValues ->
|
|
||||||
val loadingDialog = rememberLoadingDialog()
|
|
||||||
val restoreDialog = rememberConfirmDialog()
|
|
||||||
val backupDialog = rememberConfirmDialog()
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(paddingValues)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
var showRebootDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (showRebootDialog) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showRebootDialog = false },
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.reboot_required),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
text = { Text(stringResource(R.string.reboot_message)) },
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
showRebootDialog = false
|
|
||||||
reboot()
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.reboot))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { showRebootDialog = false }) {
|
|
||||||
Text(stringResource(R.string.later))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val moduleBackup = stringResource(id = R.string.module_backup)
|
|
||||||
val backupMessage = stringResource(id = R.string.module_backup_message)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Backup,
|
|
||||||
moduleBackup
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = moduleBackup,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val result = backupDialog.awaitConfirm(title = moduleBackup, content = backupMessage)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
loadingDialog.withLoading {
|
|
||||||
moduleBackup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (showRebootDialog) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showRebootDialog = false },
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.reboot_required),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
text = { Text(stringResource(R.string.reboot_message)) },
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
showRebootDialog = false
|
|
||||||
reboot()
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.reboot))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { showRebootDialog = false }) {
|
|
||||||
Text(stringResource(R.string.later))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
var useOverlayFs by rememberSaveable {
|
|
||||||
mutableStateOf(readMountSystemFile())
|
|
||||||
}
|
|
||||||
|
|
||||||
val moduleRestore = stringResource(id = R.string.module_restore)
|
|
||||||
val restoreMessage = stringResource(id = R.string.module_restore_message)
|
|
||||||
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Restore,
|
|
||||||
moduleRestore,
|
|
||||||
tint = if (useOverlayFs) MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) else MaterialTheme.colorScheme.onSurface
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = {
|
|
||||||
Text(
|
|
||||||
moduleRestore,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = if (useOverlayFs) MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) else MaterialTheme.colorScheme.onSurface
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier.clickable(
|
|
||||||
enabled = !useOverlayFs,
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
val result = restoreDialog.awaitConfirm(title = moduleRestore, content = restoreMessage)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
loadingDialog.withLoading {
|
|
||||||
moduleRestore()
|
|
||||||
showRebootDialog = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
HorizontalDivider(thickness = Dp.Hairline)
|
|
||||||
|
|
||||||
val allowlistBackup = stringResource(id = R.string.allowlist_backup)
|
|
||||||
val allowlistbackupMessage = stringResource(id = R.string.allowlist_backup_message)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Backup,
|
|
||||||
allowlistBackup
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = allowlistBackup,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val result = backupDialog.awaitConfirm(title = allowlistBackup, content = allowlistbackupMessage)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
loadingDialog.withLoading {
|
|
||||||
allowlistBackup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val allowlistRestore = stringResource(id = R.string.allowlist_restore)
|
|
||||||
val allowlistrestoreMessage = stringResource(id = R.string.allowlist_restore_message)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Restore,
|
|
||||||
allowlistRestore
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = allowlistRestore,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val result = restoreDialog.awaitConfirm(title = allowlistRestore, content = allowlistrestoreMessage)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
loadingDialog.withLoading {
|
|
||||||
allowlistRestore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
onBack: () -> Unit = {},
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.backup_restore),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
) }, navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun BackupPreview() {
|
|
||||||
BackupRestoreScreen(EmptyDestinationsNavigator)
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.*
|
|
||||||
import androidx.compose.material.icons.outlined.*
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.ModuleScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
|
|
||||||
enum class BottomBarDestination(
|
|
||||||
val direction: DirectionDestinationSpec,
|
|
||||||
@StringRes val label: Int,
|
|
||||||
val iconSelected: ImageVector,
|
|
||||||
val iconNotSelected: ImageVector,
|
|
||||||
val rootRequired: Boolean,
|
|
||||||
) {
|
|
||||||
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
|
|
||||||
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.AdminPanelSettings, Icons.Outlined.AdminPanelSettings, true),
|
|
||||||
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Layers, Icons.Outlined.Layers, true),
|
|
||||||
Settings(SettingScreenDestination, R.string.settings, Icons.Filled.Settings, Icons.Outlined.Settings, false)
|
|
||||||
}
|
|
||||||
@@ -1,359 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
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.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.*
|
|
||||||
import androidx.compose.material.icons.filled.*
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.Header
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
|
||||||
import com.maxkeppeler.sheets.list.ListDialog
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListOption
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.ksuApp
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberCustomDialog
|
|
||||||
import com.rifsxd.ksunext.ui.component.SwitchItem
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocaleHelper
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|
||||||
import com.rifsxd.ksunext.ui.util.*
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author rifsxd
|
|
||||||
* @date 2025/6/1.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun CustomizationScreen(navigator: DestinationsNavigator) {
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
onBack = dropUnlessResumed {
|
|
||||||
navigator.popBackStack()
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
snackbarHost = { SnackbarHost(snackBarHost) },
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { paddingValues ->
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(paddingValues)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
// Track language state with current app locale
|
|
||||||
var currentAppLocale by remember { mutableStateOf(LocaleHelper.getCurrentAppLocale(context)) }
|
|
||||||
|
|
||||||
// Listen for preference changes
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
currentAppLocale = LocaleHelper.getCurrentAppLocale(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Language setting with selection dialog
|
|
||||||
val languageDialog = rememberCustomDialog { dismiss ->
|
|
||||||
// Check if should use system language settings
|
|
||||||
if (LocaleHelper.useSystemLanguageSettings) {
|
|
||||||
// Android 13+ - Jump to system settings
|
|
||||||
LocaleHelper.launchSystemLanguageSettings(context)
|
|
||||||
dismiss()
|
|
||||||
} else {
|
|
||||||
// Android < 13 - Show app language selector
|
|
||||||
// Dynamically detect supported locales from resources
|
|
||||||
val supportedLocales = remember {
|
|
||||||
val locales = mutableListOf<java.util.Locale>()
|
|
||||||
|
|
||||||
// Add system default first
|
|
||||||
locales.add(java.util.Locale.ROOT) // This will represent "System Default"
|
|
||||||
|
|
||||||
// Dynamically detect available locales by checking resource directories
|
|
||||||
val resourceDirs = listOf(
|
|
||||||
"ar", "bg", "de", "fa", "fr", "hu", "in", "it",
|
|
||||||
"ja", "ko", "pl", "pt-rBR", "ru", "th", "tr",
|
|
||||||
"uk", "vi", "zh-rCN", "zh-rTW"
|
|
||||||
)
|
|
||||||
|
|
||||||
resourceDirs.forEach { dir ->
|
|
||||||
try {
|
|
||||||
val locale = when {
|
|
||||||
dir.contains("-r") -> {
|
|
||||||
val parts = dir.split("-r")
|
|
||||||
java.util.Locale.Builder()
|
|
||||||
.setLanguage(parts[0])
|
|
||||||
.setRegion(parts[1])
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
else -> java.util.Locale.Builder()
|
|
||||||
.setLanguage(dir)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if this locale has translated resources
|
|
||||||
val config = android.content.res.Configuration()
|
|
||||||
config.setLocale(locale)
|
|
||||||
val localizedContext = context.createConfigurationContext(config)
|
|
||||||
|
|
||||||
// Try to get a translated string to verify the locale is supported
|
|
||||||
val testString = localizedContext.getString(R.string.settings_language)
|
|
||||||
val defaultString = context.getString(R.string.settings_language)
|
|
||||||
|
|
||||||
// If the string is different or it's English, it's supported
|
|
||||||
if (testString != defaultString || locale.language == "en") {
|
|
||||||
locales.add(locale)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Skip unsupported locales
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by display name
|
|
||||||
val sortedLocales = locales.drop(1).sortedBy { it.getDisplayName(it) }
|
|
||||||
mutableListOf<java.util.Locale>().apply {
|
|
||||||
add(locales.first()) // System default first
|
|
||||||
addAll(sortedLocales)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val allOptions = supportedLocales.map { locale ->
|
|
||||||
val tag = if (locale == java.util.Locale.ROOT) {
|
|
||||||
"system"
|
|
||||||
} else if (locale.country.isEmpty()) {
|
|
||||||
locale.language
|
|
||||||
} else {
|
|
||||||
"${locale.language}_${locale.country}"
|
|
||||||
}
|
|
||||||
|
|
||||||
val displayName = if (locale == java.util.Locale.ROOT) {
|
|
||||||
context.getString(R.string.system_default)
|
|
||||||
} else {
|
|
||||||
locale.getDisplayName(locale)
|
|
||||||
}
|
|
||||||
|
|
||||||
tag to displayName
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentLocale = prefs.getString("app_locale", "system") ?: "system"
|
|
||||||
val options = allOptions.map { (tag, displayName) ->
|
|
||||||
ListOption(
|
|
||||||
titleText = displayName,
|
|
||||||
selected = currentLocale == tag
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedIndex by remember {
|
|
||||||
mutableIntStateOf(allOptions.indexOfFirst { (tag, _) -> currentLocale == tag })
|
|
||||||
}
|
|
||||||
|
|
||||||
ListDialog(
|
|
||||||
state = rememberUseCaseState(
|
|
||||||
visible = true,
|
|
||||||
onFinishedRequest = {
|
|
||||||
if (selectedIndex >= 0 && selectedIndex < allOptions.size) {
|
|
||||||
val newLocale = allOptions[selectedIndex].first
|
|
||||||
prefs.edit().putString("app_locale", newLocale).apply()
|
|
||||||
|
|
||||||
// Update local state immediately
|
|
||||||
currentAppLocale = LocaleHelper.getCurrentAppLocale(context)
|
|
||||||
|
|
||||||
// Apply locale change immediately for Android < 13
|
|
||||||
LocaleHelper.restartActivity(context)
|
|
||||||
}
|
|
||||||
dismiss()
|
|
||||||
},
|
|
||||||
onCloseRequest = {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
),
|
|
||||||
header = Header.Default(
|
|
||||||
title = stringResource(R.string.settings_language),
|
|
||||||
),
|
|
||||||
selection = ListSelection.Single(
|
|
||||||
showRadioButtons = true,
|
|
||||||
options = options
|
|
||||||
) { index, _ ->
|
|
||||||
selectedIndex = index
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val language = stringResource(id = R.string.settings_language)
|
|
||||||
|
|
||||||
// Compute display name based on current app locale (similar to the reference implementation)
|
|
||||||
val currentLanguageDisplay = remember(currentAppLocale) {
|
|
||||||
val locale = currentAppLocale
|
|
||||||
if (locale != null) {
|
|
||||||
locale.getDisplayName(locale)
|
|
||||||
} else {
|
|
||||||
context.getString(R.string.system_default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListItem(
|
|
||||||
leadingContent = { Icon(Icons.Filled.Translate, language) },
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = language,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
) },
|
|
||||||
supportingContent = { Text(currentLanguageDisplay) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
languageDialog.show()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var useBanner by rememberSaveable {
|
|
||||||
mutableStateOf(
|
|
||||||
prefs.getBoolean("use_banner", true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.ViewCarousel,
|
|
||||||
title = stringResource(id = R.string.settings_banner),
|
|
||||||
summary = stringResource(id = R.string.settings_banner_summary),
|
|
||||||
checked = useBanner
|
|
||||||
) {
|
|
||||||
prefs.edit().putBoolean("use_banner", it).apply()
|
|
||||||
useBanner = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var enableAmoled by rememberSaveable {
|
|
||||||
mutableStateOf(
|
|
||||||
prefs.getBoolean("enable_amoled", false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
var showRestartDialog by remember { mutableStateOf(false) }
|
|
||||||
if (isSystemInDarkTheme()) {
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.Contrast,
|
|
||||||
title = stringResource(id = R.string.settings_amoled_mode),
|
|
||||||
summary = stringResource(id = R.string.settings_amoled_mode_summary),
|
|
||||||
checked = enableAmoled
|
|
||||||
) { checked ->
|
|
||||||
prefs.edit().putBoolean("enable_amoled", checked).apply()
|
|
||||||
enableAmoled = checked
|
|
||||||
showRestartDialog = true
|
|
||||||
}
|
|
||||||
if (showRestartDialog) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showRestartDialog = false },
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.restart_required),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
text = { Text(stringResource(R.string.restart_app_message)) },
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
showRestartDialog = false
|
|
||||||
// Restart the app
|
|
||||||
val packageManager = context.packageManager
|
|
||||||
val intent = packageManager.getLaunchIntentForPackage(context.packageName)
|
|
||||||
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
context.startActivity(intent)
|
|
||||||
Runtime.getRuntime().exit(0)
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.restart_app))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { showRestartDialog = false }) {
|
|
||||||
Text(stringResource(R.string.later))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
onBack: () -> Unit = {},
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.customization),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
) }, navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun CustomizationPreview() {
|
|
||||||
CustomizationScreen(EmptyDestinationsNavigator)
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
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.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.*
|
|
||||||
import androidx.compose.material.icons.filled.*
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
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.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.ksuApp
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.SwitchItem
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|
||||||
import com.rifsxd.ksunext.ui.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author rifsxd
|
|
||||||
* @date 2025/6/15.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun DeveloperScreen(navigator: DestinationsNavigator) {
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
onBack = { navigator.popBackStack() },
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
snackbarHost = { SnackbarHost(snackBarHost) },
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { paddingValues ->
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(paddingValues)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
// --- Developer Options Switch ---
|
|
||||||
var developerOptionsEnabled by rememberSaveable {
|
|
||||||
mutableStateOf(
|
|
||||||
prefs.getBoolean("enable_developer_options", false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.DeveloperMode,
|
|
||||||
title = stringResource(id = R.string.enable_developer_options),
|
|
||||||
summary = stringResource(id = R.string.enable_developer_options_summary),
|
|
||||||
checked = developerOptionsEnabled
|
|
||||||
) {
|
|
||||||
prefs.edit().putBoolean("enable_developer_options", it).apply()
|
|
||||||
developerOptionsEnabled = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var enableWebDebugging by rememberSaveable {
|
|
||||||
mutableStateOf(
|
|
||||||
prefs.getBoolean("enable_web_debugging", false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
SwitchItem(
|
|
||||||
enabled = developerOptionsEnabled,
|
|
||||||
icon = Icons.Filled.Web,
|
|
||||||
title = stringResource(id = R.string.enable_web_debugging),
|
|
||||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
|
||||||
checked = enableWebDebugging
|
|
||||||
) {
|
|
||||||
prefs.edit().putBoolean("enable_web_debugging", it).apply()
|
|
||||||
enableWebDebugging = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
onBack: () -> Unit = {},
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.developer),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
) }, navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun DeveloperPreview() {
|
|
||||||
DeveloperScreen(EmptyDestinationsNavigator)
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Environment
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
|
||||||
import androidx.compose.foundation.layout.only
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material.icons.filled.Save
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.input.key.Key
|
|
||||||
import androidx.compose.ui.input.key.key
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.KeyEventBlocker
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|
||||||
import com.rifsxd.ksunext.ui.util.runModuleAction
|
|
||||||
import java.io.File
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Destination<RootGraph>
|
|
||||||
fun ExecuteModuleActionScreen(navigator: DestinationsNavigator, moduleId: String) {
|
|
||||||
var text by rememberSaveable { mutableStateOf("") }
|
|
||||||
var tempText: String
|
|
||||||
val logContent = rememberSaveable { StringBuilder() }
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
var actionResult: Boolean
|
|
||||||
var isActionRunning by rememberSaveable { mutableStateOf(true) }
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
// Read developer options from SharedPreferences
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
|
||||||
|
|
||||||
val view = LocalView.current
|
|
||||||
DisposableEffect(isActionRunning) {
|
|
||||||
view.keepScreenOn = isActionRunning
|
|
||||||
onDispose {
|
|
||||||
view.keepScreenOn = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(enabled = isActionRunning) {
|
|
||||||
// Disable back button if action is running
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (text.isNotEmpty()) {
|
|
||||||
return@LaunchedEffect
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
runModuleAction(
|
|
||||||
moduleId = moduleId,
|
|
||||||
onStdout = {
|
|
||||||
tempText = "$it\n"
|
|
||||||
if (tempText.startsWith("[H[J")) { // clear command
|
|
||||||
text = tempText.substring(6)
|
|
||||||
} else {
|
|
||||||
text += tempText
|
|
||||||
}
|
|
||||||
logContent.append(it).append("\n")
|
|
||||||
},
|
|
||||||
onStderr = {
|
|
||||||
logContent.append(it).append("\n")
|
|
||||||
}
|
|
||||||
).let {
|
|
||||||
actionResult = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isActionRunning = false
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
isActionRunning = isActionRunning,
|
|
||||||
onBack = dropUnlessResumed {
|
|
||||||
navigator.popBackStack()
|
|
||||||
},
|
|
||||||
onSave = {
|
|
||||||
if (!isActionRunning) {
|
|
||||||
scope.launch {
|
|
||||||
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
|
|
||||||
val date = format.format(Date())
|
|
||||||
val file = File(
|
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
|
||||||
"KernelSU_Next_module_action_log_${date}.log"
|
|
||||||
)
|
|
||||||
file.writeText(logContent.toString())
|
|
||||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
if (!isActionRunning) {
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
text = { Text(text = stringResource(R.string.close)) },
|
|
||||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
|
||||||
onClick = {
|
|
||||||
navigator.popBackStack()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing,
|
|
||||||
snackbarHost = { SnackbarHost(snackBarHost) }
|
|
||||||
) { innerPadding ->
|
|
||||||
KeyEventBlocker {
|
|
||||||
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
|
||||||
}
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize(1f)
|
|
||||||
.padding(innerPadding)
|
|
||||||
.verticalScroll(scrollState),
|
|
||||||
) {
|
|
||||||
LaunchedEffect(text) {
|
|
||||||
scrollState.animateScrollTo(scrollState.maxValue)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
text = if (developerOptionsEnabled) logContent.toString() else text,
|
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(isActionRunning: Boolean, onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.action),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
) },
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack,
|
|
||||||
enabled = !isActionRunning
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onSave,
|
|
||||||
enabled = !isActionRunning
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Save,
|
|
||||||
contentDescription = stringResource(id = R.string.save_log),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,445 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Environment
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
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.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material.icons.filled.Save
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
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.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.key.Key
|
|
||||||
import androidx.compose.ui.input.key.key
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
|
||||||
import com.rifsxd.ksunext.ui.component.ConfirmResult
|
|
||||||
import com.rifsxd.ksunext.ui.component.KeyEventBlocker
|
|
||||||
import com.rifsxd.ksunext.ui.theme.ORANGE
|
|
||||||
import com.rifsxd.ksunext.ui.theme.GREEN
|
|
||||||
import com.rifsxd.ksunext.ui.theme.RED
|
|
||||||
import com.rifsxd.ksunext.ui.util.FlashResult
|
|
||||||
import com.rifsxd.ksunext.ui.util.LkmSelection
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|
||||||
import com.rifsxd.ksunext.ui.util.flashModule
|
|
||||||
import com.rifsxd.ksunext.ui.util.installBoot
|
|
||||||
import com.rifsxd.ksunext.ui.util.reboot
|
|
||||||
import com.rifsxd.ksunext.ui.util.restoreBoot
|
|
||||||
import com.rifsxd.ksunext.ui.util.uninstallPermanently
|
|
||||||
import java.io.File
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
enum class FlashingStatus {
|
|
||||||
FLASHING,
|
|
||||||
SUCCESS,
|
|
||||||
FAILED
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.findActivity(): Activity? = when (this) {
|
|
||||||
is Activity -> this
|
|
||||||
is android.content.ContextWrapper -> baseContext.findActivity()
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lets you flash modules sequentially when mutiple zipUris are selected
|
|
||||||
fun flashModulesSequentially(
|
|
||||||
uris: List<Uri>,
|
|
||||||
onStdout: (String) -> Unit,
|
|
||||||
onStderr: (String) -> Unit
|
|
||||||
): FlashResult {
|
|
||||||
for (uri in uris) {
|
|
||||||
flashModule(uri, onStdout, onStderr).apply {
|
|
||||||
if (code != 0) {
|
|
||||||
return FlashResult(code, err, showReboot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FlashResult(0, "", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/1/1.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
@Destination<RootGraph>
|
|
||||||
fun FlashScreen(
|
|
||||||
navigator: DestinationsNavigator,
|
|
||||||
flashIt: FlashIt,
|
|
||||||
finishIntent: Boolean = false
|
|
||||||
) {
|
|
||||||
|
|
||||||
var text by rememberSaveable { mutableStateOf("") }
|
|
||||||
var tempText: String
|
|
||||||
val logContent = rememberSaveable { StringBuilder() }
|
|
||||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
var flashing by rememberSaveable {
|
|
||||||
mutableStateOf(FlashingStatus.FLASHING)
|
|
||||||
}
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
|
||||||
|
|
||||||
val activity = context.findActivity()
|
|
||||||
|
|
||||||
val view = LocalView.current
|
|
||||||
DisposableEffect(flashing) {
|
|
||||||
view.keepScreenOn = flashing == FlashingStatus.FLASHING
|
|
||||||
onDispose {
|
|
||||||
view.keepScreenOn = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(enabled = flashing == FlashingStatus.FLASHING) {
|
|
||||||
// Disable back button if flashing is running
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(enabled = flashing != FlashingStatus.FLASHING) {
|
|
||||||
navigator.popBackStack()
|
|
||||||
if (finishIntent) activity?.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
val confirmDialog = rememberConfirmDialog()
|
|
||||||
var confirmed by rememberSaveable { mutableStateOf(flashIt !is FlashIt.FlashModules) }
|
|
||||||
var pendingFlashIt by rememberSaveable { mutableStateOf<FlashIt?>(null) }
|
|
||||||
var hasFlashed by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
LaunchedEffect(flashIt) {
|
|
||||||
if (flashIt is FlashIt.FlashModules && !confirmed) {
|
|
||||||
val uris = flashIt.uris
|
|
||||||
val moduleNames =
|
|
||||||
uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }
|
|
||||||
.joinToString("")
|
|
||||||
val confirmContent =
|
|
||||||
context.getString(R.string.module_install_prompt_with_name, moduleNames)
|
|
||||||
val confirmTitle = context.getString(R.string.module)
|
|
||||||
val result = confirmDialog.awaitConfirm(
|
|
||||||
title = confirmTitle,
|
|
||||||
content = confirmContent,
|
|
||||||
markdown = true
|
|
||||||
)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
confirmed = true
|
|
||||||
pendingFlashIt = flashIt
|
|
||||||
} else {
|
|
||||||
// User cancelled, go back
|
|
||||||
navigator.popBackStack()
|
|
||||||
if (finishIntent) activity?.finish()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
confirmed = true
|
|
||||||
pendingFlashIt = flashIt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(confirmed, pendingFlashIt) {
|
|
||||||
if (!confirmed || pendingFlashIt == null || text.isNotEmpty() || hasFlashed) return@LaunchedEffect
|
|
||||||
hasFlashed = true
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
flashIt(pendingFlashIt!!, onStdout = {
|
|
||||||
tempText = "$it\n"
|
|
||||||
if (tempText.startsWith("[H[J")) { // clear command
|
|
||||||
text = tempText.substring(6)
|
|
||||||
} else {
|
|
||||||
text += tempText
|
|
||||||
}
|
|
||||||
logContent.append(it).append("\n")
|
|
||||||
}, onStderr = {
|
|
||||||
logContent.append(it).append("\n")
|
|
||||||
}).apply {
|
|
||||||
if (code != 0) {
|
|
||||||
text += "Error code: $code.\n $err Please save and check the log.\n"
|
|
||||||
}
|
|
||||||
if (showReboot) {
|
|
||||||
text += "\n\n\n"
|
|
||||||
showFloatAction = true
|
|
||||||
}
|
|
||||||
flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
flashing,
|
|
||||||
onBack = dropUnlessResumed {
|
|
||||||
navigator.popBackStack()
|
|
||||||
if (finishIntent) activity?.finish()
|
|
||||||
},
|
|
||||||
onSave = {
|
|
||||||
scope.launch {
|
|
||||||
val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
|
|
||||||
val date = format.format(Date())
|
|
||||||
val file = File(
|
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
|
||||||
"KernelSU_Next_install_log_${date}.log"
|
|
||||||
)
|
|
||||||
file.writeText(logContent.toString())
|
|
||||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
if (flashIt is FlashIt.FlashModules && (flashing == FlashingStatus.SUCCESS)) {
|
|
||||||
// Reboot button for modules flashing
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
reboot()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = { Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.reboot)) },
|
|
||||||
text = { Text(text = stringResource(R.string.reboot)) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flashIt is FlashIt.FlashModules && (flashing == FlashingStatus.FAILED)) {
|
|
||||||
// Close button for modules flashing
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
text = { Text(text = stringResource(R.string.close)) },
|
|
||||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
|
||||||
onClick = {
|
|
||||||
navigator.popBackStack()
|
|
||||||
if (finishIntent) activity?.finish()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flashIt is FlashIt.FlashBoot && (flashing == FlashingStatus.SUCCESS || flashing == FlashingStatus.FAILED)) {
|
|
||||||
val isLocalPatch = flashIt.boot != null && !flashIt.ota
|
|
||||||
val isDirectOrOta = flashIt.boot == null || flashIt.ota
|
|
||||||
|
|
||||||
if (flashing == FlashingStatus.FAILED) {
|
|
||||||
// Always show close on failure
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
text = { Text(text = stringResource(R.string.close)) },
|
|
||||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
|
||||||
onClick = {
|
|
||||||
navigator.popBackStack()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else if (flashing == FlashingStatus.SUCCESS) {
|
|
||||||
if (isLocalPatch) {
|
|
||||||
// Local patching: show only Close
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
text = { Text(text = stringResource(R.string.close)) },
|
|
||||||
icon = { Icon(Icons.Filled.Close, contentDescription = null) },
|
|
||||||
onClick = {
|
|
||||||
navigator.popBackStack()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else if (isDirectOrOta) {
|
|
||||||
// Direct install or OTA inactive slot: show only Reboot
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
reboot()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = { Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.reboot)) },
|
|
||||||
text = { Text(text = stringResource(R.string.reboot)) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing,
|
|
||||||
snackbarHost = { SnackbarHost(hostState = snackBarHost) }
|
|
||||||
) { innerPadding ->
|
|
||||||
KeyEventBlocker {
|
|
||||||
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
|
||||||
}
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize(1f)
|
|
||||||
.padding(innerPadding)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(scrollState),
|
|
||||||
) {
|
|
||||||
LaunchedEffect(text) {
|
|
||||||
scrollState.animateScrollTo(scrollState.maxValue)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
text = if (developerOptionsEnabled) logContent.toString() else text,
|
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Uri.getFileName(context: Context): String {
|
|
||||||
val contentResolver = context.contentResolver
|
|
||||||
val cursor = contentResolver.query(this, null, null, null, null)
|
|
||||||
return cursor?.use {
|
|
||||||
val nameIndex = it.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
|
|
||||||
if (it.moveToFirst() && nameIndex != -1) {
|
|
||||||
it.getString(nameIndex)
|
|
||||||
} else {
|
|
||||||
this.lastPathSegment ?: "unknown.zip"
|
|
||||||
}
|
|
||||||
} ?: (this.lastPathSegment ?: "unknown.zip")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
sealed class FlashIt : Parcelable {
|
|
||||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
|
|
||||||
FlashIt()
|
|
||||||
|
|
||||||
data class FlashModules(val uris: List<Uri>) : FlashIt()
|
|
||||||
|
|
||||||
data object FlashRestore : FlashIt()
|
|
||||||
|
|
||||||
data object FlashUninstall : FlashIt()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun flashIt(
|
|
||||||
flashIt: FlashIt,
|
|
||||||
onStdout: (String) -> Unit,
|
|
||||||
onStderr: (String) -> Unit
|
|
||||||
): FlashResult {
|
|
||||||
return when (flashIt) {
|
|
||||||
is FlashIt.FlashBoot -> installBoot(
|
|
||||||
flashIt.boot,
|
|
||||||
flashIt.lkm,
|
|
||||||
flashIt.ota,
|
|
||||||
onStdout,
|
|
||||||
onStderr
|
|
||||||
)
|
|
||||||
|
|
||||||
is FlashIt.FlashModules -> {
|
|
||||||
flashModulesSequentially(flashIt.uris, onStdout, onStderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
FlashIt.FlashRestore -> restoreBoot(onStdout, onStderr)
|
|
||||||
|
|
||||||
FlashIt.FlashUninstall -> uninstallPermanently(onStdout, onStderr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
status: FlashingStatus,
|
|
||||||
onBack: () -> Unit = {},
|
|
||||||
onSave: () -> Unit = {},
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
when (status) {
|
|
||||||
FlashingStatus.FLASHING -> R.string.flashing
|
|
||||||
FlashingStatus.SUCCESS -> R.string.flash_success
|
|
||||||
FlashingStatus.FAILED -> R.string.flash_failed
|
|
||||||
}
|
|
||||||
),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
color = when (status) {
|
|
||||||
FlashingStatus.FLASHING -> ORANGE
|
|
||||||
FlashingStatus.SUCCESS -> GREEN
|
|
||||||
FlashingStatus.FAILED -> RED
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = { if (status != FlashingStatus.FLASHING) onBack() },
|
|
||||||
enabled = status != FlashingStatus.FLASHING
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(
|
|
||||||
onClick = { if (status != FlashingStatus.FLASHING) onSave() },
|
|
||||||
enabled = status != FlashingStatus.FLASHING
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Save,
|
|
||||||
contentDescription = "Localized description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun InstallPreview() {
|
|
||||||
InstallScreen(EmptyDestinationsNavigator)
|
|
||||||
}
|
|
||||||
@@ -1,885 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.PowerManager
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.system.Os
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.animation.*
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
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.automirrored.filled.*
|
|
||||||
import androidx.compose.material.icons.filled.*
|
|
||||||
import androidx.compose.material.icons.outlined.*
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.intl.Locale
|
|
||||||
import androidx.compose.ui.text.toUpperCase
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.content.pm.PackageInfoCompat
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
|
||||||
import com.dergoogler.mmrl.ui.component.LabelItemDefaults
|
|
||||||
import com.dergoogler.mmrl.ui.component.text.TextRow
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import com.rifsxd.ksunext.*
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
|
||||||
import com.rifsxd.ksunext.ui.util.*
|
|
||||||
import com.rifsxd.ksunext.ui.util.module.LatestVersionInfo
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>(start = true)
|
|
||||||
@Composable
|
|
||||||
fun HomeScreen(navigator: DestinationsNavigator) {
|
|
||||||
val kernelVersion = getKernelVersion()
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
kernelVersion,
|
|
||||||
ksuVersion,
|
|
||||||
onInstallClick = {
|
|
||||||
navigator.navigate(InstallScreenDestination)
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { innerPadding ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(innerPadding)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
val lkmMode = ksuVersion?.let {
|
|
||||||
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusCard(kernelVersion, ksuVersion, lkmMode) {
|
|
||||||
navigator.navigate(InstallScreenDestination)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ksuVersion != null && rootAvailable()) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(IntrinsicSize.Min),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(14.dp)
|
|
||||||
) {
|
|
||||||
Box(modifier = Modifier.weight(1f)) { SuperuserCard() }
|
|
||||||
Box(modifier = Modifier.weight(1f)) { ModuleCard() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isManager && Natives.requireNewKernel()) {
|
|
||||||
WarningCard(
|
|
||||||
stringResource(id = R.string.require_kernel_version).format(
|
|
||||||
ksuVersion, Natives.MINIMAL_SUPPORTED_KERNEL
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (ksuVersion != null && !rootAvailable()) {
|
|
||||||
WarningCard(
|
|
||||||
stringResource(id = R.string.grant_root_failed)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val checkUpdate =
|
|
||||||
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("check_update", false)
|
|
||||||
if (checkUpdate) {
|
|
||||||
UpdateCard()
|
|
||||||
}
|
|
||||||
//NextCard()
|
|
||||||
InfoCard(autoExpand = developerOptionsEnabled)
|
|
||||||
IssueReportCard()
|
|
||||||
//EXperimentalCard()
|
|
||||||
Spacer(Modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SuperuserCard() {
|
|
||||||
val count = getSuperuserCount()
|
|
||||||
ElevatedCard(
|
|
||||||
colors = CardDefaults.elevatedCardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(16.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = if (count <= 1) {
|
|
||||||
stringResource(R.string.home_superuser_count_singular)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.home_superuser_count_plural)
|
|
||||||
},
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = count.toString(),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ModuleCard() {
|
|
||||||
val count = getModuleCount()
|
|
||||||
ElevatedCard(
|
|
||||||
colors = CardDefaults.elevatedCardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(16.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = if (count <= 1) {
|
|
||||||
stringResource(R.string.home_module_count_singular)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.home_module_count_plural)
|
|
||||||
},
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = count.toString(),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun UpdateCard() {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val latestVersionInfo = LatestVersionInfo()
|
|
||||||
val newVersion by produceState(initialValue = latestVersionInfo) {
|
|
||||||
value = withContext(Dispatchers.IO) {
|
|
||||||
checkNewVersion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentVersionCode = getManagerVersion(context).second
|
|
||||||
val newVersionCode = newVersion.versionCode
|
|
||||||
val newVersionUrl = newVersion.downloadUrl
|
|
||||||
val changelog = newVersion.changelog
|
|
||||||
|
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
val title = stringResource(id = R.string.module_changelog)
|
|
||||||
val updateText = stringResource(id = R.string.module_update)
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = newVersionCode > currentVersionCode,
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = shrinkVertically() + fadeOut()
|
|
||||||
) {
|
|
||||||
val updateDialog = rememberConfirmDialog(onConfirm = { uriHandler.openUri(newVersionUrl) })
|
|
||||||
WarningCard(
|
|
||||||
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
|
|
||||||
MaterialTheme.colorScheme.outlineVariant
|
|
||||||
) {
|
|
||||||
if (changelog.isEmpty()) {
|
|
||||||
uriHandler.openUri(newVersionUrl)
|
|
||||||
} else {
|
|
||||||
updateDialog.showConfirm(
|
|
||||||
title = title,
|
|
||||||
content = changelog,
|
|
||||||
markdown = true,
|
|
||||||
confirm = updateText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
|
||||||
DropdownMenuItem(text = {
|
|
||||||
Text(stringResource(id))
|
|
||||||
}, onClick = {
|
|
||||||
reboot(reason)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun getSeasonalIcon(): ImageVector {
|
|
||||||
val month = Calendar.getInstance().get(Calendar.MONTH) // 0-11 for January-December
|
|
||||||
return when (month) {
|
|
||||||
Calendar.DECEMBER, Calendar.JANUARY, Calendar.FEBRUARY -> Icons.Filled.AcUnit // Winter
|
|
||||||
Calendar.MARCH, Calendar.APRIL, Calendar.MAY -> Icons.Filled.Spa // Spring
|
|
||||||
Calendar.JUNE, Calendar.JULY, Calendar.AUGUST -> Icons.Filled.WbSunny // Summer
|
|
||||||
Calendar.SEPTEMBER, Calendar.OCTOBER, Calendar.NOVEMBER -> Icons.Filled.Forest // Fall
|
|
||||||
else -> Icons.Filled.Whatshot // Fallback icon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
kernelVersion: KernelVersion,
|
|
||||||
ksuVersion: Int?,
|
|
||||||
onInstallClick: () -> Unit,
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
var isSpinning by remember { mutableStateOf(false) }
|
|
||||||
val rotation by animateFloatAsState(
|
|
||||||
targetValue = if (isSpinning) 360f else 0f,
|
|
||||||
animationSpec = tween(durationMillis = 800),
|
|
||||||
finishedListener = {
|
|
||||||
isSpinning = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
isSpinning = true
|
|
||||||
}
|
|
||||||
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.clickable(
|
|
||||||
indication = null,
|
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
|
||||||
) {
|
|
||||||
if (!isSpinning) isSpinning = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = getSeasonalIcon(),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
.graphicsLayer {
|
|
||||||
rotationZ = rotation
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.app_name),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
if (kernelVersion.isGKI()) {
|
|
||||||
IconButton(onClick = onInstallClick) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Archive,
|
|
||||||
contentDescription = stringResource(id = R.string.install)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
var showDropdown by remember { mutableStateOf(false) }
|
|
||||||
IconButton(onClick = {
|
|
||||||
showDropdown = true
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.PowerSettingsNew,
|
|
||||||
contentDescription = stringResource(id = R.string.reboot)
|
|
||||||
)
|
|
||||||
|
|
||||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
|
||||||
showDropdown = false
|
|
||||||
}) {
|
|
||||||
RebootDropdownItem(id = R.string.reboot)
|
|
||||||
|
|
||||||
val pm =
|
|
||||||
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
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_recovery, reason = "recovery")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun StatusCard(
|
|
||||||
kernelVersion: KernelVersion,
|
|
||||||
ksuVersion: Int?,
|
|
||||||
lkmMode: Boolean?,
|
|
||||||
moduleUpdateCount: Int = 0,
|
|
||||||
onClickInstall: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
var tapCount by remember { mutableStateOf(0) }
|
|
||||||
|
|
||||||
ElevatedCard(
|
|
||||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
|
||||||
if (ksuVersion != null) MaterialTheme.colorScheme.primaryContainer
|
|
||||||
else MaterialTheme.colorScheme.errorContainer
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
tapCount++
|
|
||||||
if (tapCount == 5) {
|
|
||||||
Toast.makeText(context, "What are you doing? 🤔", Toast.LENGTH_SHORT).show()
|
|
||||||
} else if (tapCount == 10) {
|
|
||||||
Toast.makeText(context, "Never gonna give you up! 💜", Toast.LENGTH_SHORT).show()
|
|
||||||
val url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
||||||
val intent = android.content.Intent(android.content.Intent.ACTION_VIEW, android.net.Uri.parse(url))
|
|
||||||
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
context.startActivity(intent)
|
|
||||||
} else if (kernelVersion.isGKI()) {
|
|
||||||
onClickInstall()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "Something weird happened... 🤔", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
} else if (ksuVersion == null && kernelVersion.isGKI()) {
|
|
||||||
onClickInstall()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
when {
|
|
||||||
ksuVersion != null -> {
|
|
||||||
val workingMode = when {
|
|
||||||
lkmMode == true -> "LKM"
|
|
||||||
lkmMode == false || kernelVersion.isGKI() -> "GKI2"
|
|
||||||
lkmMode == null && kernelVersion.isULegacy() -> "U-LEGACY"
|
|
||||||
lkmMode == null && kernelVersion.isLegacy() -> "LEGACY"
|
|
||||||
lkmMode == null && kernelVersion.isGKI1() -> "GKI1"
|
|
||||||
else -> "NON-STANDARD"
|
|
||||||
}
|
|
||||||
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.CheckCircle,
|
|
||||||
contentDescription = stringResource(R.string.home_working)
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(start = 20.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
val labelStyle = LabelItemDefaults.style
|
|
||||||
TextRow(
|
|
||||||
trailingContent = {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
|
||||||
) {
|
|
||||||
LabelItem(
|
|
||||||
icon = if (Natives.isSafeMode) {
|
|
||||||
{
|
|
||||||
Icon(
|
|
||||||
tint = labelStyle.contentColor,
|
|
||||||
imageVector = Icons.Filled.Security,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(
|
|
||||||
text = workingMode,
|
|
||||||
style = labelStyle.textStyle.copy(color = labelStyle.contentColor),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (isSuCompatDisabled()) {
|
|
||||||
LabelItem(
|
|
||||||
icon = {
|
|
||||||
Icon(
|
|
||||||
tint = labelStyle.contentColor,
|
|
||||||
imageVector = Icons.Filled.Warning,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.sucompat_disabled),
|
|
||||||
style = labelStyle.textStyle.copy(
|
|
||||||
color = labelStyle.contentColor,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.home_working),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kernelVersion.isGKI() -> {
|
|
||||||
Icon(Icons.Filled.NewReleases, stringResource(R.string.home_not_installed))
|
|
||||||
Column(Modifier.padding(start = 20.dp)) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_not_installed),
|
|
||||||
style = MaterialTheme.typography.titleMedium
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_click_to_install),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
Icon(Icons.Filled.Cancel, stringResource(R.string.home_failure))
|
|
||||||
Column(Modifier.padding(start = 20.dp)) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_failure),
|
|
||||||
style = MaterialTheme.typography.titleMedium
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_failure_tip),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun WarningCard(
|
|
||||||
message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null
|
|
||||||
) {
|
|
||||||
ElevatedCard(
|
|
||||||
colors = CardDefaults.elevatedCardColors(
|
|
||||||
containerColor = color
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.then(onClick?.let { Modifier.clickable { it() } } ?: Modifier)
|
|
||||||
.padding(24.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = message, style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun InfoCard(autoExpand: Boolean = false) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
|
||||||
|
|
||||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val developerOptionsEnabled = prefs.getBoolean("enable_developer_options", false)
|
|
||||||
|
|
||||||
LaunchedEffect(autoExpand) {
|
|
||||||
if (autoExpand) {
|
|
||||||
expanded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevatedCard {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 24.dp)
|
|
||||||
) {
|
|
||||||
@Composable
|
|
||||||
fun InfoCardItem(label: String, content: String, icon: Any? = null) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
if (icon != null) {
|
|
||||||
when (icon) {
|
|
||||||
is ImageVector -> Icon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.padding(end = 20.dp)
|
|
||||||
)
|
|
||||||
is Painter -> Icon(
|
|
||||||
painter = icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.padding(end = 20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = content,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
modifier = Modifier.padding(top = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
val managerVersion = getManagerVersion(context)
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.home_manager_version),
|
|
||||||
content = if (
|
|
||||||
developerOptionsEnabled &&
|
|
||||||
Natives.version >= Natives.MINIMAL_SUPPORTED_MANAGER_UID
|
|
||||||
) {
|
|
||||||
"${managerVersion.first} (${managerVersion.second}) | UID: ${Natives.getManagerUid()}"
|
|
||||||
} else {
|
|
||||||
"${managerVersion.first} (${managerVersion.second})"
|
|
||||||
},
|
|
||||||
icon = painterResource(R.drawable.ic_ksu_next),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (ksuVersion != null &&
|
|
||||||
Natives.version >= Natives.MINIMAL_SUPPORTED_HOOK_MODE) {
|
|
||||||
|
|
||||||
val hookMode =
|
|
||||||
Natives.getHookMode()
|
|
||||||
.takeUnless { it.isNullOrBlank() }
|
|
||||||
?: stringResource(R.string.unavailable)
|
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.hook_mode),
|
|
||||||
content = hookMode,
|
|
||||||
icon = Icons.Filled.Phishing,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.home_mount_system),
|
|
||||||
content = currentMountSystem().ifEmpty { stringResource(R.string.unavailable) },
|
|
||||||
icon = Icons.Filled.SettingsSuggest,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
val suSFS = getSuSFS()
|
|
||||||
if (suSFS == "Supported") {
|
|
||||||
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
|
|
||||||
val susSUMode = if (isSUS_SU) {
|
|
||||||
val mode = susfsSUS_SU_Mode()
|
|
||||||
val modeString =
|
|
||||||
if (mode == "2") stringResource(R.string.enabled) else stringResource(R.string.disabled)
|
|
||||||
"| SuS SU: $modeString"
|
|
||||||
} else ""
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.home_susfs_version),
|
|
||||||
content = "${stringResource(R.string.susfs_supported)} | ${getSuSFSVersion()} (${getSuSFSVariant()}) $susSUMode",
|
|
||||||
icon = painterResource(R.drawable.ic_sus),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Natives.isZygiskEnabled()) {
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.zygisk_status),
|
|
||||||
content = stringResource(R.string.enabled),
|
|
||||||
icon = Icons.Filled.Vaccines
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!expanded) {
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Center
|
|
||||||
) {
|
|
||||||
IconButton(
|
|
||||||
onClick = { expanded = true },
|
|
||||||
modifier = Modifier.size(36.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.KeyboardArrowDown,
|
|
||||||
contentDescription = "Show more"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedVisibility(visible = expanded) {
|
|
||||||
val uname = Os.uname()
|
|
||||||
Column {
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.home_kernel),
|
|
||||||
content = "${uname.release} (${uname.machine})",
|
|
||||||
icon = painterResource(R.drawable.ic_linux),
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.home_android),
|
|
||||||
content = "${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})",
|
|
||||||
icon = Icons.Filled.Android,
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.home_abi),
|
|
||||||
content = Build.SUPPORTED_ABIS.joinToString(", "),
|
|
||||||
icon = Icons.Filled.Memory,
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
InfoCardItem(
|
|
||||||
label = stringResource(R.string.home_selinux_status),
|
|
||||||
content = getSELinuxStatus(),
|
|
||||||
icon = Icons.Filled.Security,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NextCard() {
|
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
val url = stringResource(R.string.home_next_kernelsu_repo)
|
|
||||||
|
|
||||||
ElevatedCard {
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
uriHandler.openUri(url)
|
|
||||||
}
|
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_next_kernelsu),
|
|
||||||
style = MaterialTheme.typography.titleSmall
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_next_kernelsu_body),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun EXperimentalCard() {
|
|
||||||
/*val uriHandler = LocalUriHandler.current
|
|
||||||
val url = stringResource(R.string.home_experimental_kernelsu_repo)
|
|
||||||
*/
|
|
||||||
|
|
||||||
ElevatedCard {
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
/*.clickable {
|
|
||||||
uriHandler.openUri(url)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
.padding(24.dp), verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_experimental_kernelsu),
|
|
||||||
style = MaterialTheme.typography.titleSmall
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_experimental_kernelsu_body),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_experimental_kernelsu_body_point_1),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(2.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_experimental_kernelsu_body_point_2),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(2.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.home_experimental_kernelsu_body_point_3),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun IssueReportCard() {
|
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
val githubIssueUrl = stringResource(R.string.issue_report_github_link)
|
|
||||||
val telegramUrl = stringResource(R.string.issue_report_telegram_link)
|
|
||||||
|
|
||||||
ElevatedCard {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(24.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.issue_report_title),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.issue_report_body),
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.issue_report_body_2),
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
IconButton(onClick = { uriHandler.openUri(githubIssueUrl) }) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_github),
|
|
||||||
contentDescription = stringResource(R.string.issue_report_github),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(onClick = { uriHandler.openUri(telegramUrl) }) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_telegram),
|
|
||||||
contentDescription = stringResource(R.string.issue_report_telegram),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getManagerVersion(context: Context): Pair<String, Long> {
|
|
||||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)!!
|
|
||||||
val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
|
|
||||||
return Pair(packageInfo.versionName!!, versionCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun StatusCardPreview() {
|
|
||||||
Column {
|
|
||||||
StatusCard(KernelVersion(5, 10, 101), 1, null)
|
|
||||||
StatusCard(KernelVersion(5, 10, 101), 20000, true)
|
|
||||||
StatusCard(KernelVersion(5, 10, 101), null, true)
|
|
||||||
StatusCard(KernelVersion(4, 10, 101), null, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun WarningCardPreview() {
|
|
||||||
Column {
|
|
||||||
WarningCard(message = "Warning message")
|
|
||||||
WarningCard(
|
|
||||||
message = "Warning message ",
|
|
||||||
MaterialTheme.colorScheme.outlineVariant,
|
|
||||||
onClick = {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,400 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.LocalIndication
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
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.fillMaxWidth
|
|
||||||
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.selection.toggleable
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.FileUpload
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
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.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.semantics.Role
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.Header
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
|
||||||
import com.maxkeppeler.sheets.list.ListDialog
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListOption
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.DialogHandle
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberCustomDialog
|
|
||||||
import com.rifsxd.ksunext.ui.util.LkmSelection
|
|
||||||
import com.rifsxd.ksunext.ui.util.getCurrentKmi
|
|
||||||
import com.rifsxd.ksunext.ui.util.getSupportedKmis
|
|
||||||
import com.rifsxd.ksunext.ui.util.isAbDevice
|
|
||||||
import com.rifsxd.ksunext.ui.util.isInitBoot
|
|
||||||
import com.rifsxd.ksunext.ui.util.rootAvailable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2024/3/12.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun InstallScreen(navigator: DestinationsNavigator) {
|
|
||||||
var showLkmWarning by rememberSaveable { mutableStateOf(true) }
|
|
||||||
|
|
||||||
if (showLkmWarning) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = {
|
|
||||||
showLkmWarning = false
|
|
||||||
navigator.popBackStack()
|
|
||||||
},
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.warning),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
text = { Text(stringResource(R.string.lkm_warning_message)) },
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { showLkmWarning = false }) {
|
|
||||||
Text(stringResource(R.string.proceed))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
showLkmWarning = false
|
|
||||||
navigator.popBackStack()
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.cancel))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var installMethod by remember {
|
|
||||||
mutableStateOf<InstallMethod?>(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
var lkmSelection by remember {
|
|
||||||
mutableStateOf<LkmSelection>(LkmSelection.KmiNone)
|
|
||||||
}
|
|
||||||
|
|
||||||
val onInstall = {
|
|
||||||
installMethod?.let { method ->
|
|
||||||
val flashIt = FlashIt.FlashBoot(
|
|
||||||
boot = if (method is InstallMethod.SelectFile) method.uri else null,
|
|
||||||
lkm = lkmSelection,
|
|
||||||
ota = method is InstallMethod.DirectInstallToInactiveSlot
|
|
||||||
)
|
|
||||||
navigator.navigate(FlashScreenDestination(flashIt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentKmi by produceState(initialValue = "") { value = getCurrentKmi() }
|
|
||||||
|
|
||||||
val selectKmiDialog = rememberSelectKmiDialog { kmi ->
|
|
||||||
kmi?.let {
|
|
||||||
lkmSelection = LkmSelection.KmiString(it)
|
|
||||||
onInstall()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val onClickNext = {
|
|
||||||
if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
|
|
||||||
// no lkm file selected and cannot get current kmi
|
|
||||||
selectKmiDialog.show()
|
|
||||||
} else {
|
|
||||||
onInstall()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val selectLkmLauncher =
|
|
||||||
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
|
|
||||||
if (it.resultCode == Activity.RESULT_OK) {
|
|
||||||
it.data?.data?.let { uri ->
|
|
||||||
lkmSelection = LkmSelection.LkmUri(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val onLkmUpload = {
|
|
||||||
selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
||||||
type = "application/octet-stream"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
onBack = dropUnlessResumed { navigator.popBackStack() },
|
|
||||||
onLkmUpload = onLkmUpload,
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { innerPadding ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(innerPadding)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
SelectInstallMethod { method ->
|
|
||||||
installMethod = method
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
(lkmSelection as? LkmSelection.LkmUri)?.let {
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
id = R.string.selected_lkm,
|
|
||||||
it.uri.lastPathSegment ?: "(file)"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Button(modifier = Modifier.fillMaxWidth(),
|
|
||||||
enabled = installMethod != null,
|
|
||||||
onClick = {
|
|
||||||
onClickNext()
|
|
||||||
}) {
|
|
||||||
Text(
|
|
||||||
stringResource(id = R.string.install_next),
|
|
||||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class InstallMethod {
|
|
||||||
data class SelectFile(
|
|
||||||
val uri: Uri? = null,
|
|
||||||
@StringRes override val label: Int = R.string.select_file,
|
|
||||||
override val summary: String?
|
|
||||||
) : InstallMethod()
|
|
||||||
|
|
||||||
data object DirectInstall : InstallMethod() {
|
|
||||||
override val label: Int
|
|
||||||
get() = R.string.direct_install
|
|
||||||
}
|
|
||||||
|
|
||||||
data object DirectInstallToInactiveSlot : InstallMethod() {
|
|
||||||
override val label: Int
|
|
||||||
get() = R.string.install_inactive_slot
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract val label: Int
|
|
||||||
open val summary: String? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|
||||||
val rootAvailable = rootAvailable()
|
|
||||||
val isAbDevice = isAbDevice()
|
|
||||||
val selectFileTip = stringResource(
|
|
||||||
id = R.string.select_file_tip, if (isInitBoot()) "init_boot/vendor_boot" else "boot"
|
|
||||||
)
|
|
||||||
val radioOptions =
|
|
||||||
mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
|
|
||||||
if (rootAvailable) {
|
|
||||||
radioOptions.add(InstallMethod.DirectInstall)
|
|
||||||
|
|
||||||
if (isAbDevice) {
|
|
||||||
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
|
|
||||||
val selectImageLauncher = rememberLauncherForActivityResult(
|
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
|
||||||
) {
|
|
||||||
if (it.resultCode == Activity.RESULT_OK) {
|
|
||||||
it.data?.data?.let { uri ->
|
|
||||||
val option = InstallMethod.SelectFile(uri, summary = selectFileTip)
|
|
||||||
selectedOption = option
|
|
||||||
onSelected(option)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val confirmDialog = rememberConfirmDialog(onConfirm = {
|
|
||||||
selectedOption = InstallMethod.DirectInstallToInactiveSlot
|
|
||||||
onSelected(InstallMethod.DirectInstallToInactiveSlot)
|
|
||||||
}, onDismiss = null)
|
|
||||||
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
|
|
||||||
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
|
|
||||||
|
|
||||||
val onClick = { option: InstallMethod ->
|
|
||||||
|
|
||||||
when (option) {
|
|
||||||
is InstallMethod.SelectFile -> {
|
|
||||||
selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
||||||
type = "application/octet-stream"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
is InstallMethod.DirectInstall -> {
|
|
||||||
selectedOption = option
|
|
||||||
onSelected(option)
|
|
||||||
}
|
|
||||||
|
|
||||||
is InstallMethod.DirectInstallToInactiveSlot -> {
|
|
||||||
confirmDialog.showConfirm(dialogTitle, dialogContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
radioOptions.forEach { option ->
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.toggleable(
|
|
||||||
value = option.javaClass == selectedOption?.javaClass,
|
|
||||||
onValueChange = {
|
|
||||||
onClick(option)
|
|
||||||
},
|
|
||||||
role = Role.RadioButton,
|
|
||||||
indication = LocalIndication.current,
|
|
||||||
interactionSource = interactionSource
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
RadioButton(
|
|
||||||
selected = option.javaClass == selectedOption?.javaClass,
|
|
||||||
onClick = {
|
|
||||||
onClick(option)
|
|
||||||
},
|
|
||||||
interactionSource = interactionSource
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(vertical = 12.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = option.label),
|
|
||||||
fontSize = MaterialTheme.typography.titleMedium.fontSize,
|
|
||||||
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
|
|
||||||
fontStyle = MaterialTheme.typography.titleMedium.fontStyle
|
|
||||||
)
|
|
||||||
option.summary?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
|
||||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
|
||||||
fontStyle = MaterialTheme.typography.bodySmall.fontStyle
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
|
|
||||||
return rememberCustomDialog { dismiss ->
|
|
||||||
val supportedKmi by produceState(initialValue = emptyList<String>()) {
|
|
||||||
value = getSupportedKmis()
|
|
||||||
}
|
|
||||||
val options = supportedKmi.map { value ->
|
|
||||||
ListOption(
|
|
||||||
titleText = value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var selection by remember { mutableStateOf<String?>(null) }
|
|
||||||
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
|
||||||
onSelected(selection)
|
|
||||||
}, onCloseRequest = {
|
|
||||||
dismiss()
|
|
||||||
}), header = Header.Default(
|
|
||||||
title = stringResource(R.string.select_kmi),
|
|
||||||
), selection = ListSelection.Single(
|
|
||||||
showRadioButtons = true,
|
|
||||||
options = options,
|
|
||||||
) { _, option ->
|
|
||||||
selection = option.titleText
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
onBack: () -> Unit = {},
|
|
||||||
onLkmUpload: () -> Unit = {},
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.install),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
) }, navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
}, actions = {
|
|
||||||
IconButton(onClick = onLkmUpload) {
|
|
||||||
Icon(Icons.Filled.FileUpload, contentDescription = null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Preview
|
|
||||||
fun SelectInstallPreview() {
|
|
||||||
InstallScreen(EmptyDestinationsNavigator)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,659 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
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.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.Undo
|
|
||||||
import androidx.compose.material.icons.filled.*
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.LineHeightStyle
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.Header
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.IconSource
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
|
||||||
import com.maxkeppeler.sheets.list.ListDialog
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListOption
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.BackupRestoreScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.CustomizationScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.DeveloperScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import com.rifsxd.ksunext.BuildConfig
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.ksuApp
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.AboutDialog
|
|
||||||
import com.rifsxd.ksunext.ui.component.ConfirmResult
|
|
||||||
import com.rifsxd.ksunext.ui.component.DialogHandle
|
|
||||||
import com.rifsxd.ksunext.ui.component.SwitchItem
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberCustomDialog
|
|
||||||
import com.rifsxd.ksunext.ui.component.rememberLoadingDialog
|
|
||||||
import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|
||||||
import com.rifsxd.ksunext.ui.util.getBugreportFile
|
|
||||||
import com.rifsxd.ksunext.ui.util.*
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/1/1.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun SettingScreen(navigator: DestinationsNavigator) {
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopBar(
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
snackbarHost = { SnackbarHost(snackBarHost) },
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { paddingValues ->
|
|
||||||
val aboutDialog = rememberCustomDialog {
|
|
||||||
AboutDialog(it)
|
|
||||||
}
|
|
||||||
val loadingDialog = rememberLoadingDialog()
|
|
||||||
val shrinkDialog = rememberConfirmDialog()
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(paddingValues)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val exportBugreportLauncher = rememberLauncherForActivityResult(
|
|
||||||
ActivityResultContracts.CreateDocument("application/gzip")
|
|
||||||
) { uri: Uri? ->
|
|
||||||
if (uri == null) return@rememberLauncherForActivityResult
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
loadingDialog.show()
|
|
||||||
context.contentResolver.openOutputStream(uri)?.use { output ->
|
|
||||||
getBugreportFile(context).inputStream().use {
|
|
||||||
it.copyTo(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadingDialog.hide()
|
|
||||||
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
ListItem(
|
|
||||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = profileTemplate,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var umountChecked by rememberSaveable {
|
|
||||||
mutableStateOf(Natives.isDefaultUmountModules())
|
|
||||||
}
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.FolderDelete,
|
|
||||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
|
||||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
|
||||||
checked = umountChecked
|
|
||||||
|
|
||||||
) {
|
|
||||||
if (Natives.setDefaultUmountModules(it)) {
|
|
||||||
umountChecked = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
|
||||||
var isSuDisabled by rememberSaveable {
|
|
||||||
mutableStateOf(!Natives.isSuEnabled())
|
|
||||||
}
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.RemoveModerator,
|
|
||||||
title = stringResource(id = R.string.settings_disable_su),
|
|
||||||
summary = stringResource(id = R.string.settings_disable_su_summary),
|
|
||||||
checked = isSuDisabled
|
|
||||||
) { checked ->
|
|
||||||
val shouldEnable = !checked
|
|
||||||
if (Natives.setSuEnabled(shouldEnable)) {
|
|
||||||
isSuDisabled = !shouldEnable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
val suSFS = getSuSFS()
|
|
||||||
val isSUS_SU = getSuSFSFeatures()
|
|
||||||
if (suSFS == "Supported") {
|
|
||||||
if (isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") {
|
|
||||||
var isEnabled by rememberSaveable {
|
|
||||||
mutableStateOf(susfsSUS_SU_Mode() == "2")
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
isEnabled = susfsSUS_SU_Mode() == "2"
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.VisibilityOff,
|
|
||||||
title = stringResource(id = R.string.settings_susfs_toggle),
|
|
||||||
summary = stringResource(id = R.string.settings_susfs_toggle_summary),
|
|
||||||
checked = isEnabled
|
|
||||||
) {
|
|
||||||
if (it) {
|
|
||||||
susfsSUS_SU_2()
|
|
||||||
} else {
|
|
||||||
susfsSUS_SU_0()
|
|
||||||
}
|
|
||||||
prefs.edit().putBoolean("enable_sus_su", it).apply()
|
|
||||||
isEnabled = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var useOverlayFs by rememberSaveable {
|
|
||||||
mutableStateOf(readMountSystemFile())
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
useOverlayFs = readMountSystemFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
var showRebootDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val isOverlayAvailable = overlayFsAvailable()
|
|
||||||
|
|
||||||
if (ksuVersion != null && isOverlayAvailable) {
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.Build,
|
|
||||||
title = stringResource(id = R.string.use_overlay_fs),
|
|
||||||
summary = stringResource(id = R.string.use_overlay_fs_summary),
|
|
||||||
checked = useOverlayFs
|
|
||||||
) {
|
|
||||||
prefs.edit().putBoolean("use_overlay_fs", it).apply()
|
|
||||||
useOverlayFs = it
|
|
||||||
if (useOverlayFs) {
|
|
||||||
moduleBackup()
|
|
||||||
updateMountSystemFile(true)
|
|
||||||
} else {
|
|
||||||
moduleMigration()
|
|
||||||
updateMountSystemFile(false)
|
|
||||||
}
|
|
||||||
if (isManager) install()
|
|
||||||
showRebootDialog = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showRebootDialog) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showRebootDialog = false },
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.reboot_required),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
text = { Text(stringResource(R.string.reboot_message)) },
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
showRebootDialog = false
|
|
||||||
reboot()
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.reboot))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { showRebootDialog = false }) {
|
|
||||||
Text(stringResource(R.string.later))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var checkUpdate by rememberSaveable {
|
|
||||||
mutableStateOf(
|
|
||||||
prefs.getBoolean("check_update", false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SwitchItem(
|
|
||||||
icon = Icons.Filled.Update,
|
|
||||||
title = stringResource(id = R.string.settings_check_update),
|
|
||||||
summary = stringResource(id = R.string.settings_check_update_summary),
|
|
||||||
checked = checkUpdate
|
|
||||||
) {
|
|
||||||
prefs.edit().putBoolean("check_update", it).apply()
|
|
||||||
checkUpdate = it
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOverlayAvailable && useOverlayFs) {
|
|
||||||
val shrink = stringResource(id = R.string.shrink_sparse_image)
|
|
||||||
val shrinkMessage = stringResource(id = R.string.shrink_sparse_image_message)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Compress,
|
|
||||||
shrink
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = shrink,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val result = shrinkDialog.awaitConfirm(title = shrink, content = shrinkMessage)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
loadingDialog.withLoading {
|
|
||||||
shrinkModules()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val customization = stringResource(id = R.string.customization)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Palette,
|
|
||||||
customization
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = customization,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
navigator.navigate(CustomizationScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
val backupRestore = stringResource(id = R.string.backup_restore)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Backup,
|
|
||||||
backupRestore
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = backupRestore,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
navigator.navigate(BackupRestoreScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val developer = stringResource(id = R.string.developer)
|
|
||||||
if (ksuVersion != null) {
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.DeveloperBoard,
|
|
||||||
developer
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = developer,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
navigator.navigate(DeveloperScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
|
||||||
if (lkmMode) {
|
|
||||||
UninstallItem(navigator) {
|
|
||||||
loadingDialog.withLoading(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var showBottomsheet by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.BugReport,
|
|
||||||
stringResource(id = R.string.export_log)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = stringResource(id = R.string.export_log),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
showBottomsheet = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (showBottomsheet) {
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = { showBottomsheet = false },
|
|
||||||
content = {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(10.dp)
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
|
|
||||||
) {
|
|
||||||
Box {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.clickable {
|
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm")
|
|
||||||
val current = LocalDateTime.now().format(formatter)
|
|
||||||
exportBugreportLauncher.launch("KernelSU_Next_bugreport_${current}.tar.gz")
|
|
||||||
showBottomsheet = false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Save,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.save_log),
|
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
|
||||||
textAlign = TextAlign.Center.also {
|
|
||||||
LineHeightStyle(
|
|
||||||
alignment = LineHeightStyle.Alignment.Center,
|
|
||||||
trim = LineHeightStyle.Trim.None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Box {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.clickable {
|
|
||||||
scope.launch {
|
|
||||||
val bugreport = loadingDialog.withLoading {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
getBugreportFile(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val uri: Uri =
|
|
||||||
FileProvider.getUriForFile(
|
|
||||||
context,
|
|
||||||
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
|
||||||
bugreport
|
|
||||||
)
|
|
||||||
|
|
||||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
|
||||||
putExtra(Intent.EXTRA_STREAM, uri)
|
|
||||||
setDataAndType(uri, "application/gzip")
|
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.startActivity(
|
|
||||||
Intent.createChooser(
|
|
||||||
shareIntent,
|
|
||||||
context.getString(R.string.send_log)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Share,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.send_log),
|
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
|
||||||
textAlign = TextAlign.Center.also {
|
|
||||||
LineHeightStyle(
|
|
||||||
alignment = LineHeightStyle.Alignment.Center,
|
|
||||||
trim = LineHeightStyle.Trim.None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val about = stringResource(id = R.string.about)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.ContactPage,
|
|
||||||
about
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = about,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
aboutDialog.show()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun UninstallItem(
|
|
||||||
navigator: DestinationsNavigator,
|
|
||||||
withLoading: suspend (suspend () -> Unit) -> Unit,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val uninstallConfirmDialog = rememberConfirmDialog()
|
|
||||||
val showTodo = {
|
|
||||||
Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
val uninstallDialog = rememberUninstallDialog { uninstallType ->
|
|
||||||
scope.launch {
|
|
||||||
val result = uninstallConfirmDialog.awaitConfirm(
|
|
||||||
title = context.getString(uninstallType.title),
|
|
||||||
content = context.getString(uninstallType.message)
|
|
||||||
)
|
|
||||||
if (result == ConfirmResult.Confirmed) {
|
|
||||||
withLoading {
|
|
||||||
when (uninstallType) {
|
|
||||||
UninstallType.TEMPORARY -> showTodo()
|
|
||||||
UninstallType.PERMANENT -> navigator.navigate(
|
|
||||||
FlashScreenDestination(FlashIt.FlashUninstall)
|
|
||||||
)
|
|
||||||
UninstallType.RESTORE_STOCK_IMAGE -> navigator.navigate(
|
|
||||||
FlashScreenDestination(FlashIt.FlashRestore)
|
|
||||||
)
|
|
||||||
UninstallType.NONE -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val uninstall = stringResource(id = R.string.settings_uninstall)
|
|
||||||
ListItem(
|
|
||||||
leadingContent = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Delete,
|
|
||||||
uninstall
|
|
||||||
)
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = uninstall,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
uninstallDialog.show()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class UninstallType(val title: Int, val message: Int, val icon: ImageVector) {
|
|
||||||
TEMPORARY(
|
|
||||||
R.string.settings_uninstall_temporary,
|
|
||||||
R.string.settings_uninstall_temporary_message,
|
|
||||||
Icons.Filled.Delete
|
|
||||||
),
|
|
||||||
PERMANENT(
|
|
||||||
R.string.settings_uninstall_permanent,
|
|
||||||
R.string.settings_uninstall_permanent_message,
|
|
||||||
Icons.Filled.DeleteForever
|
|
||||||
),
|
|
||||||
RESTORE_STOCK_IMAGE(
|
|
||||||
R.string.settings_restore_stock_image,
|
|
||||||
R.string.settings_restore_stock_image_message,
|
|
||||||
Icons.AutoMirrored.Filled.Undo
|
|
||||||
),
|
|
||||||
NONE(0, 0, Icons.Filled.Delete)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
|
||||||
return rememberCustomDialog { dismiss ->
|
|
||||||
val options = listOf(
|
|
||||||
// UninstallType.TEMPORARY,
|
|
||||||
UninstallType.PERMANENT,
|
|
||||||
UninstallType.RESTORE_STOCK_IMAGE
|
|
||||||
)
|
|
||||||
val listOptions = options.map {
|
|
||||||
ListOption(
|
|
||||||
titleText = stringResource(it.title),
|
|
||||||
subtitleText = if (it.message != 0) stringResource(it.message) else null,
|
|
||||||
icon = IconSource(it.icon)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var selection = UninstallType.NONE
|
|
||||||
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
|
||||||
if (selection != UninstallType.NONE) {
|
|
||||||
onSelected(selection)
|
|
||||||
}
|
|
||||||
}, onCloseRequest = {
|
|
||||||
dismiss()
|
|
||||||
}), header = Header.Default(
|
|
||||||
title = stringResource(R.string.settings_uninstall),
|
|
||||||
), selection = ListSelection.Single(
|
|
||||||
showRadioButtons = false,
|
|
||||||
options = listOptions,
|
|
||||||
) { index, _ ->
|
|
||||||
selection = options[index]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.settings),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
) },
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun SettingsPreview() {
|
|
||||||
SettingScreen(EmptyDestinationsNavigator)
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
|
||||||
import com.dergoogler.mmrl.ui.component.LabelItemDefaults
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.SearchAppBar
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.SuperUserViewModel
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|
||||||
val viewModel = viewModel<SuperUserViewModel>()
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
val listState = rememberLazyListState()
|
|
||||||
|
|
||||||
LaunchedEffect(navigator) {
|
|
||||||
viewModel.search = ""
|
|
||||||
if (viewModel.appList.isEmpty()) {
|
|
||||||
viewModel.fetchAppList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
SearchAppBar(
|
|
||||||
title = { Text(
|
|
||||||
text = stringResource(R.string.superuser),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
) },
|
|
||||||
searchText = viewModel.search,
|
|
||||||
onSearchTextChange = { viewModel.search = it },
|
|
||||||
onClearClick = { viewModel.search = "" },
|
|
||||||
dropdownContent = {
|
|
||||||
var showDropdown by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = { showDropdown = true },
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.MoreVert,
|
|
||||||
contentDescription = stringResource(id = R.string.settings)
|
|
||||||
)
|
|
||||||
|
|
||||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
|
||||||
showDropdown = false
|
|
||||||
}) {
|
|
||||||
DropdownMenuItem(text = {
|
|
||||||
Text(stringResource(R.string.refresh))
|
|
||||||
}, onClick = {
|
|
||||||
scope.launch {
|
|
||||||
viewModel.fetchAppList()
|
|
||||||
}
|
|
||||||
showDropdown = false
|
|
||||||
})
|
|
||||||
DropdownMenuItem(text = {
|
|
||||||
Text(
|
|
||||||
if (viewModel.showSystemApps) {
|
|
||||||
stringResource(R.string.hide_system_apps)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.show_system_apps)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}, onClick = {
|
|
||||||
viewModel.updateShowSystemApps(!viewModel.showSystemApps)
|
|
||||||
showDropdown = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { innerPadding ->
|
|
||||||
PullToRefreshBox(
|
|
||||||
modifier = Modifier.padding(innerPadding),
|
|
||||||
onRefresh = {
|
|
||||||
scope.launch { viewModel.fetchAppList() }
|
|
||||||
},
|
|
||||||
isRefreshing = viewModel.isRefreshing
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
) {
|
|
||||||
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
|
||||||
AppItem(app) {
|
|
||||||
navigator.navigate(AppProfileScreenDestination(app))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
|
||||||
private fun AppItem(
|
|
||||||
app: SuperUserViewModel.AppInfo,
|
|
||||||
onClickListener: () -> Unit,
|
|
||||||
) {
|
|
||||||
ListItem(
|
|
||||||
modifier = Modifier.clickable(onClick = onClickListener),
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = app.label,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
) },
|
|
||||||
supportingContent = {
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = app.packageName,
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
|
|
||||||
FlowRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
if (app.allowSu) {
|
|
||||||
LabelItem(
|
|
||||||
text = "ROOT",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
if (Natives.uidShouldUmount(app.uid)) {
|
|
||||||
LabelItem(
|
|
||||||
text = "UMOUNT",
|
|
||||||
style = LabelItemDefaults.style.copy(
|
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (app.hasCustomProfile) {
|
|
||||||
LabelItem(
|
|
||||||
text = "CUSTOM",
|
|
||||||
style = LabelItemDefaults.style.copy(
|
|
||||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else if (!app.allowSu && !Natives.uidShouldUmount(app.uid)) {
|
|
||||||
LabelItem(
|
|
||||||
text = "DEFAULT",
|
|
||||||
style = LabelItemDefaults.style.copy(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leadingContent = {
|
|
||||||
AsyncImage(
|
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
|
||||||
.data(app.packageInfo)
|
|
||||||
.crossfade(true)
|
|
||||||
.build(),
|
|
||||||
contentDescription = app.label,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.width(48.dp)
|
|
||||||
.height(48.dp)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LabelText(label: String) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 4.dp, end = 4.dp)
|
|
||||||
.background(
|
|
||||||
Color.Black,
|
|
||||||
shape = RoundedCornerShape(4.dp)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 8.sp,
|
|
||||||
color = Color.White,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,333 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.only
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.Add
|
|
||||||
import androidx.compose.material.icons.filled.ImportExport
|
|
||||||
import androidx.compose.material.icons.filled.Sync
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.dergoogler.mmrl.ui.component.LabelItem
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import com.ramcosta.composedestinations.result.ResultRecipient
|
|
||||||
import com.ramcosta.composedestinations.result.getOr
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.TemplateViewModel
|
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.runtime.snapshotFlow
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.scaleIn
|
|
||||||
import androidx.compose.animation.scaleOut
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/10/20.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun AppProfileTemplateScreen(
|
|
||||||
navigator: DestinationsNavigator,
|
|
||||||
resultRecipient: ResultRecipient<TemplateEditorScreenDestination, Boolean>
|
|
||||||
) {
|
|
||||||
val viewModel = viewModel<TemplateViewModel>()
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (viewModel.templateList.isEmpty()) {
|
|
||||||
viewModel.fetchTemplates()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle result from TemplateEditorScreen, refresh if needed
|
|
||||||
resultRecipient.onNavResult { result ->
|
|
||||||
if (result.getOr { false }) {
|
|
||||||
scope.launch { viewModel.fetchTemplates() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
|
||||||
var showFab by remember { mutableStateOf(true) }
|
|
||||||
|
|
||||||
LaunchedEffect(listState) {
|
|
||||||
var lastIndex = listState.firstVisibleItemIndex
|
|
||||||
var lastOffset = listState.firstVisibleItemScrollOffset
|
|
||||||
|
|
||||||
snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
|
|
||||||
.collect { (currIndex, currOffset) ->
|
|
||||||
val isScrollingDown = currIndex > lastIndex ||
|
|
||||||
(currIndex == lastIndex && currOffset > lastOffset + 4)
|
|
||||||
val isScrollingUp = currIndex < lastIndex ||
|
|
||||||
(currIndex == lastIndex && currOffset < lastOffset - 4)
|
|
||||||
|
|
||||||
when {
|
|
||||||
isScrollingDown && showFab -> showFab = false
|
|
||||||
isScrollingUp && !showFab -> showFab = true
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndex = currIndex
|
|
||||||
lastOffset = currOffset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
val showToast = fun(msg: String) {
|
|
||||||
scope.launch(Dispatchers.Main) {
|
|
||||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TopBar(
|
|
||||||
onBack = dropUnlessResumed { navigator.popBackStack() },
|
|
||||||
onSync = {
|
|
||||||
scope.launch { viewModel.fetchTemplates(true) }
|
|
||||||
},
|
|
||||||
onImport = {
|
|
||||||
clipboardManager.getText()?.text?.let {
|
|
||||||
if (it.isEmpty()) {
|
|
||||||
showToast(context.getString(R.string.app_profile_template_import_empty))
|
|
||||||
return@let
|
|
||||||
}
|
|
||||||
scope.launch {
|
|
||||||
viewModel.importTemplates(
|
|
||||||
it, {
|
|
||||||
showToast(context.getString(R.string.app_profile_template_import_success))
|
|
||||||
viewModel.fetchTemplates(false)
|
|
||||||
},
|
|
||||||
showToast
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onExport = {
|
|
||||||
scope.launch {
|
|
||||||
viewModel.exportTemplates(
|
|
||||||
{
|
|
||||||
showToast(context.getString(R.string.app_profile_template_export_empty))
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
clipboardManager.setText(AnnotatedString(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = showFab,
|
|
||||||
enter = scaleIn(
|
|
||||||
animationSpec = tween(200),
|
|
||||||
initialScale = 0.8f
|
|
||||||
) + fadeIn(animationSpec = tween(400)),
|
|
||||||
exit = scaleOut(
|
|
||||||
animationSpec = tween(200),
|
|
||||||
targetScale = 0.8f
|
|
||||||
) + fadeOut(animationSpec = tween(400))
|
|
||||||
) {
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
onClick = {
|
|
||||||
navigator.navigate(
|
|
||||||
TemplateEditorScreenDestination(
|
|
||||||
TemplateViewModel.TemplateInfo(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
icon = { Icon(Icons.Filled.Add, null) },
|
|
||||||
text = { Text(stringResource(id = R.string.app_profile_template_create)) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { innerPadding ->
|
|
||||||
PullToRefreshBox(
|
|
||||||
modifier = Modifier.padding(innerPadding),
|
|
||||||
isRefreshing = viewModel.isRefreshing,
|
|
||||||
onRefresh = {
|
|
||||||
scope.launch { viewModel.fetchTemplates() }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
|
||||||
contentPadding = remember {
|
|
||||||
PaddingValues(bottom = 16.dp /* Scaffold Fab Spacing + Fab container height */)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
items(viewModel.templateList, key = { it.id }) { app ->
|
|
||||||
TemplateItem(navigator, app)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
|
||||||
private fun TemplateItem(
|
|
||||||
navigator: DestinationsNavigator,
|
|
||||||
template: TemplateViewModel.TemplateInfo
|
|
||||||
) {
|
|
||||||
ListItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
navigator.navigate(TemplateEditorScreenDestination(template, !template.local))
|
|
||||||
},
|
|
||||||
headlineContent = { Text(
|
|
||||||
text = template.name,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
) },
|
|
||||||
supportingContent = {
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = "${template.id}${if (template.author.isEmpty()) "" else "@${template.author}"}",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
|
||||||
)
|
|
||||||
Text(template.description)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
|
|
||||||
FlowRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
LabelItem(text = "UID: ${template.uid}")
|
|
||||||
LabelItem(text = "GID: ${template.gid}")
|
|
||||||
LabelItem(text = template.context)
|
|
||||||
if (template.local) {
|
|
||||||
LabelItem(text = "local")
|
|
||||||
} else {
|
|
||||||
LabelItem(text = "remote")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
onBack: () -> Unit,
|
|
||||||
onSync: () -> Unit = {},
|
|
||||||
onImport: () -> Unit = {},
|
|
||||||
onExport: () -> Unit = {},
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.settings_profile_template),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = onSync) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Sync,
|
|
||||||
contentDescription = stringResource(id = R.string.app_profile_template_sync)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var showDropdown by remember { mutableStateOf(false) }
|
|
||||||
IconButton(onClick = {
|
|
||||||
showDropdown = true
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.ImportExport,
|
|
||||||
contentDescription = stringResource(id = R.string.app_profile_import_export)
|
|
||||||
)
|
|
||||||
|
|
||||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
|
||||||
showDropdown = false
|
|
||||||
}) {
|
|
||||||
DropdownMenuItem(text = {
|
|
||||||
Text(stringResource(id = R.string.app_profile_import_from_clipboard))
|
|
||||||
}, onClick = {
|
|
||||||
onImport()
|
|
||||||
showDropdown = false
|
|
||||||
})
|
|
||||||
DropdownMenuItem(text = {
|
|
||||||
Text(stringResource(id = R.string.app_profile_export_to_clipboard))
|
|
||||||
}, onClick = {
|
|
||||||
onExport()
|
|
||||||
showDropdown = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,345 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.screen
|
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
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.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.DeleteForever
|
|
||||||
import androidx.compose.material.icons.filled.Save
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
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.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
|
||||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
|
||||||
import com.rifsxd.ksunext.Natives
|
|
||||||
import com.rifsxd.ksunext.R
|
|
||||||
import com.rifsxd.ksunext.ui.component.profile.RootProfileConfig
|
|
||||||
import com.rifsxd.ksunext.ui.util.deleteAppProfileTemplate
|
|
||||||
import com.rifsxd.ksunext.ui.util.getAppProfileTemplate
|
|
||||||
import com.rifsxd.ksunext.ui.util.setAppProfileTemplate
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.TemplateViewModel
|
|
||||||
import com.rifsxd.ksunext.ui.viewmodel.toJSON
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/10/20.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
|
|
||||||
@Destination<RootGraph>
|
|
||||||
@Composable
|
|
||||||
fun TemplateEditorScreen(
|
|
||||||
navigator: ResultBackNavigator<Boolean>,
|
|
||||||
initialTemplate: TemplateViewModel.TemplateInfo,
|
|
||||||
readOnly: Boolean = true,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val isCreation = initialTemplate.id.isBlank()
|
|
||||||
val autoSave = !isCreation
|
|
||||||
|
|
||||||
var template by rememberSaveable {
|
|
||||||
mutableStateOf(initialTemplate)
|
|
||||||
}
|
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
|
|
||||||
BackHandler {
|
|
||||||
navigator.navigateBack(result = !readOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
val author =
|
|
||||||
if (initialTemplate.author.isNotEmpty()) "@${initialTemplate.author}" else ""
|
|
||||||
val readOnlyHint = if (readOnly) {
|
|
||||||
" - ${stringResource(id = R.string.app_profile_template_readonly)}"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
val titleSummary = "${initialTemplate.id}$author$readOnlyHint"
|
|
||||||
val saveTemplateFailed = stringResource(id = R.string.app_profile_template_save_failed)
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
TopBar(
|
|
||||||
title = if (isCreation) {
|
|
||||||
stringResource(R.string.app_profile_template_create)
|
|
||||||
} else if (readOnly) {
|
|
||||||
stringResource(R.string.app_profile_template_view)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.app_profile_template_edit)
|
|
||||||
},
|
|
||||||
readOnly = readOnly,
|
|
||||||
summary = titleSummary,
|
|
||||||
onBack = dropUnlessResumed { navigator.navigateBack(result = !readOnly) },
|
|
||||||
onDelete = {
|
|
||||||
if (deleteAppProfileTemplate(template.id)) {
|
|
||||||
navigator.navigateBack(result = true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSave = {
|
|
||||||
if (saveTemplate(template, isCreation)) {
|
|
||||||
navigator.navigateBack(result = true)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, saveTemplateFailed, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
},
|
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
|
||||||
) { innerPadding ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(innerPadding)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.pointerInteropFilter {
|
|
||||||
// disable click and ripple if readOnly
|
|
||||||
readOnly
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (isCreation) {
|
|
||||||
var errorHint by remember {
|
|
||||||
mutableStateOf("")
|
|
||||||
}
|
|
||||||
val idConflictError = stringResource(id = R.string.app_profile_template_id_exist)
|
|
||||||
val idInvalidError = stringResource(id = R.string.app_profile_template_id_invalid)
|
|
||||||
TextEdit(
|
|
||||||
label = stringResource(id = R.string.app_profile_template_id),
|
|
||||||
text = template.id,
|
|
||||||
errorHint = errorHint,
|
|
||||||
isError = errorHint.isNotEmpty()
|
|
||||||
) { value ->
|
|
||||||
errorHint = if (isTemplateExist(value)) {
|
|
||||||
idConflictError
|
|
||||||
} else if (!isValidTemplateId(value)) {
|
|
||||||
idInvalidError
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
template = template.copy(id = value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextEdit(
|
|
||||||
label = stringResource(id = R.string.app_profile_template_name),
|
|
||||||
text = template.name
|
|
||||||
) { value ->
|
|
||||||
template.copy(name = value).run {
|
|
||||||
if (autoSave) {
|
|
||||||
if (!saveTemplate(this)) {
|
|
||||||
// failed
|
|
||||||
return@run
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template = this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TextEdit(
|
|
||||||
label = stringResource(id = R.string.app_profile_template_description),
|
|
||||||
text = template.description
|
|
||||||
) { value ->
|
|
||||||
template.copy(description = value).run {
|
|
||||||
if (autoSave) {
|
|
||||||
if (!saveTemplate(this)) {
|
|
||||||
// failed
|
|
||||||
return@run
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template = this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RootProfileConfig(fixedName = true,
|
|
||||||
profile = toNativeProfile(template),
|
|
||||||
onProfileChange = {
|
|
||||||
template.copy(
|
|
||||||
uid = it.uid,
|
|
||||||
gid = it.gid,
|
|
||||||
groups = it.groups,
|
|
||||||
capabilities = it.capabilities,
|
|
||||||
context = it.context,
|
|
||||||
namespace = it.namespace,
|
|
||||||
rules = it.rules.split("\n")
|
|
||||||
).run {
|
|
||||||
if (autoSave) {
|
|
||||||
if (!saveTemplate(this)) {
|
|
||||||
// failed
|
|
||||||
return@run
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template = this
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toNativeProfile(templateInfo: TemplateViewModel.TemplateInfo): Natives.Profile {
|
|
||||||
return Natives.Profile().copy(rootTemplate = templateInfo.id,
|
|
||||||
uid = templateInfo.uid,
|
|
||||||
gid = templateInfo.gid,
|
|
||||||
groups = templateInfo.groups,
|
|
||||||
capabilities = templateInfo.capabilities,
|
|
||||||
context = templateInfo.context,
|
|
||||||
namespace = templateInfo.namespace,
|
|
||||||
rules = templateInfo.rules.joinToString("\n").ifBlank { "" })
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isTemplateValid(template: TemplateViewModel.TemplateInfo): Boolean {
|
|
||||||
if (template.id.isBlank()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidTemplateId(template.id)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveTemplate(template: TemplateViewModel.TemplateInfo, isCreation: Boolean = false): Boolean {
|
|
||||||
if (!isTemplateValid(template)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCreation && isTemplateExist(template.id)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val json = template.toJSON()
|
|
||||||
json.put("local", true)
|
|
||||||
return setAppProfileTemplate(template.id, json.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
title: String,
|
|
||||||
readOnly: Boolean,
|
|
||||||
summary: String = "",
|
|
||||||
onBack: () -> Unit,
|
|
||||||
onDelete: () -> Unit = {},
|
|
||||||
onSave: () -> Unit = {},
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
)
|
|
||||||
if (summary.isNotBlank()) {
|
|
||||||
Text(
|
|
||||||
text = summary,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
}, actions = {
|
|
||||||
if (readOnly) {
|
|
||||||
return@TopAppBar
|
|
||||||
}
|
|
||||||
IconButton(onClick = onDelete) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.DeleteForever,
|
|
||||||
contentDescription = stringResource(id = R.string.app_profile_template_delete)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(onClick = onSave) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Save,
|
|
||||||
contentDescription = stringResource(id = R.string.app_profile_template_save)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TextEdit(
|
|
||||||
label: String,
|
|
||||||
text: String,
|
|
||||||
errorHint: String = "",
|
|
||||||
isError: Boolean = false,
|
|
||||||
onValueChange: (String) -> Unit = {}
|
|
||||||
) {
|
|
||||||
ListItem(headlineContent = {
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
OutlinedTextField(
|
|
||||||
value = text,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
label = { Text(label) },
|
|
||||||
suffix = {
|
|
||||||
if (errorHint.isNotBlank()) {
|
|
||||||
Text(
|
|
||||||
text = if (isError) errorHint else "",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isError = isError,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Next
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(onDone = {
|
|
||||||
keyboardController?.hide()
|
|
||||||
}),
|
|
||||||
onValueChange = onValueChange
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isValidTemplateId(id: String): Boolean {
|
|
||||||
return Regex("""^([A-Za-z][A-Za-z\d_]*\.)*[A-Za-z][A-Za-z\d_]*$""").matches(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isTemplateExist(id: String): Boolean {
|
|
||||||
return getAppProfileTemplate(id).isNotBlank()
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
val PRIMARY = Color(0xFF8AADF4) // Catppuccin Blue
|
|
||||||
val PRIMARY_LIGHT = Color(0xFFB7BDF8) // Catppuccin Lavender
|
|
||||||
val SECONDARY_LIGHT = Color(0xFFA6DA95) // Catppuccin Green
|
|
||||||
|
|
||||||
val PRIMARY_DARK = Color(0xFF7DC4E4) // Catppuccin Sky
|
|
||||||
val SECONDARY_DARK = Color(0xFFF5BDE6) // Catppuccin Pink
|
|
||||||
|
|
||||||
val AMOLED_BLACK = Color(0xFF000000) // Pure black for AMOLED
|
|
||||||
|
|
||||||
val DARK_PURPLE = Color(0xFF6E6CB6) // Catppuccin Mauve (dark purple)
|
|
||||||
val DARK_GREY = Color(0xFF363A4F) // Catppuccin Surface (dark grey)
|
|
||||||
|
|
||||||
val GREEN = Color(0xFF4CAF50) // Green
|
|
||||||
val RED = Color(0xFFF44336) // Red
|
|
||||||
val YELLOW = Color(0xFFFFEB3B) // Yellow
|
|
||||||
val ORANGE = Color(0xFFFF9800) // Orange
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.theme
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.activity.SystemBarStyle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.darkColorScheme
|
|
||||||
import androidx.compose.material3.dynamicDarkColorScheme
|
|
||||||
import androidx.compose.material3.dynamicLightColorScheme
|
|
||||||
import androidx.compose.material3.lightColorScheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.SideEffect
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
|
||||||
primary = PRIMARY,
|
|
||||||
secondary = PRIMARY_DARK,
|
|
||||||
tertiary = SECONDARY_DARK
|
|
||||||
)
|
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
|
||||||
primary = PRIMARY,
|
|
||||||
secondary = PRIMARY_LIGHT,
|
|
||||||
tertiary = SECONDARY_LIGHT
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Color.blend(other: Color, ratio: Float): Color {
|
|
||||||
val inverse = 1f - ratio
|
|
||||||
return Color(
|
|
||||||
red = red * inverse + other.red * ratio,
|
|
||||||
green = green * inverse + other.green * ratio,
|
|
||||||
blue = blue * inverse + other.blue * ratio,
|
|
||||||
alpha = alpha
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun KernelSUTheme(
|
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
|
||||||
// Dynamic color is available on Android 12+
|
|
||||||
dynamicColor: Boolean = true,
|
|
||||||
amoledMode: Boolean = false,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
val colorScheme = when {
|
|
||||||
amoledMode && darkTheme && dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val dynamicScheme = dynamicDarkColorScheme(context)
|
|
||||||
dynamicScheme.copy(
|
|
||||||
background = AMOLED_BLACK,
|
|
||||||
surface = AMOLED_BLACK,
|
|
||||||
surfaceVariant = dynamicScheme.surfaceVariant.blend(AMOLED_BLACK, 0.6f),
|
|
||||||
surfaceContainer = dynamicScheme.surfaceContainer.blend(AMOLED_BLACK, 0.6f),
|
|
||||||
surfaceContainerLow = dynamicScheme.surfaceContainerLow.blend(AMOLED_BLACK, 0.6f),
|
|
||||||
surfaceContainerLowest = dynamicScheme.surfaceContainerLowest.blend(AMOLED_BLACK, 0.6f),
|
|
||||||
surfaceContainerHigh = dynamicScheme.surfaceContainerHigh.blend(AMOLED_BLACK, 0.6f),
|
|
||||||
surfaceContainerHighest = dynamicScheme.surfaceContainerHighest.blend(AMOLED_BLACK, 0.6f),
|
|
||||||
primaryContainer = dynamicScheme.primaryContainer.blend(AMOLED_BLACK, 0.6f),
|
|
||||||
secondaryContainer = dynamicScheme.secondaryContainer.blend(AMOLED_BLACK, 0.6f),
|
|
||||||
tertiaryContainer = dynamicScheme.tertiaryContainer.blend(AMOLED_BLACK, 0.6f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
|
||||||
val context = LocalContext.current
|
|
||||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
|
||||||
}
|
|
||||||
amoledMode && darkTheme -> {
|
|
||||||
DarkColorScheme.copy(
|
|
||||||
background = AMOLED_BLACK,
|
|
||||||
surface = AMOLED_BLACK,
|
|
||||||
surfaceVariant = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
|
||||||
surfaceContainer = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
|
||||||
surfaceContainerLow = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
|
||||||
surfaceContainerLowest = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
|
||||||
surfaceContainerHigh = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
|
||||||
surfaceContainerHighest = DARK_GREY.blend(AMOLED_BLACK, 0.8f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
darkTheme -> DarkColorScheme
|
|
||||||
else -> LightColorScheme
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemBarStyle(
|
|
||||||
darkMode = darkTheme
|
|
||||||
)
|
|
||||||
|
|
||||||
MaterialTheme(
|
|
||||||
colorScheme = colorScheme,
|
|
||||||
typography = Typography,
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SystemBarStyle(
|
|
||||||
darkMode: Boolean,
|
|
||||||
statusBarScrim: Color = Color.Transparent,
|
|
||||||
navigationBarScrim: Color = Color.Transparent,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val activity = context as ComponentActivity
|
|
||||||
|
|
||||||
SideEffect {
|
|
||||||
activity.enableEdgeToEdge(
|
|
||||||
statusBarStyle = SystemBarStyle.auto(
|
|
||||||
statusBarScrim.toArgb(),
|
|
||||||
statusBarScrim.toArgb(),
|
|
||||||
) { darkMode },
|
|
||||||
navigationBarStyle = when {
|
|
||||||
darkMode -> SystemBarStyle.dark(
|
|
||||||
navigationBarScrim.toArgb()
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> SystemBarStyle.light(
|
|
||||||
navigationBarScrim.toArgb(),
|
|
||||||
navigationBarScrim.toArgb(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
|
||||||
val Typography = androidx.compose.material3.Typography(
|
|
||||||
bodyLarge = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
lineHeight = 24.sp,
|
|
||||||
letterSpacing = 0.5.sp
|
|
||||||
)
|
|
||||||
/* Other default text styles to override
|
|
||||||
titleLarge = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 22.sp,
|
|
||||||
lineHeight = 28.sp,
|
|
||||||
letterSpacing = 0.sp
|
|
||||||
),
|
|
||||||
labelSmall = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
fontSize = 11.sp,
|
|
||||||
lineHeight = 16.sp,
|
|
||||||
letterSpacing = 0.5.sp
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.util
|
|
||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
|
||||||
|
|
||||||
val LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {
|
|
||||||
error("CompositionLocal LocalSnackbarController not present")
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package com.rifsxd.ksunext.ui.util
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.DownloadManager
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Environment
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import com.rifsxd.ksunext.ksuApp
|
|
||||||
import com.rifsxd.ksunext.ui.util.module.LatestVersionInfo
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/6/22.
|
|
||||||
*/
|
|
||||||
@SuppressLint("Range")
|
|
||||||
fun download(
|
|
||||||
context: Context,
|
|
||||||
url: String,
|
|
||||||
fileName: String,
|
|
||||||
description: String,
|
|
||||||
onDownloaded: (Uri) -> Unit = {},
|
|
||||||
onDownloading: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
|
||||||
|
|
||||||
val query = DownloadManager.Query()
|
|
||||||
query.setFilterByStatus(DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PENDING)
|
|
||||||
downloadManager.query(query).use { cursor ->
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
val uri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI))
|
|
||||||
val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
|
|
||||||
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
|
||||||
val columnTitle = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE))
|
|
||||||
if (url == uri || fileName == columnTitle) {
|
|
||||||
if (status == DownloadManager.STATUS_RUNNING || status == DownloadManager.STATUS_PENDING) {
|
|
||||||
onDownloading()
|
|
||||||
return
|
|
||||||
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
|
||||||
onDownloaded(Uri.parse(localUri))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val request = DownloadManager.Request(Uri.parse(url))
|
|
||||||
.setDestinationInExternalPublicDir(
|
|
||||||
Environment.DIRECTORY_DOWNLOADS,
|
|
||||||
fileName
|
|
||||||
)
|
|
||||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
|
||||||
.setMimeType("application/zip")
|
|
||||||
.setTitle(fileName)
|
|
||||||
.setDescription(description)
|
|
||||||
|
|
||||||
downloadManager.enqueue(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkNewVersion(): LatestVersionInfo {
|
|
||||||
// Next version updates
|
|
||||||
val url = "https://api.github.com/repos/KernelSU-Next/KernelSU-Next/releases/latest"
|
|
||||||
// default null value if failed
|
|
||||||
val defaultValue = LatestVersionInfo()
|
|
||||||
runCatching {
|
|
||||||
ksuApp.okhttpClient.newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
|
||||||
.use { response ->
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
val body = response.body?.string() ?: return defaultValue
|
|
||||||
val json = org.json.JSONObject(body)
|
|
||||||
val changelog = json.optString("body")
|
|
||||||
|
|
||||||
val assets = json.getJSONArray("assets")
|
|
||||||
for (i in 0 until assets.length()) {
|
|
||||||
val asset = assets.getJSONObject(i)
|
|
||||||
val name = asset.getString("name")
|
|
||||||
if (!name.endsWith(".apk")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val regex = Regex("v(.+?)_(\\d+)-")
|
|
||||||
val matchResult = regex.find(name) ?: continue
|
|
||||||
val versionName = matchResult.groupValues[1]
|
|
||||||
val versionCode = matchResult.groupValues[2].toInt()
|
|
||||||
val downloadUrl = asset.getString("browser_download_url")
|
|
||||||
|
|
||||||
return LatestVersionInfo(
|
|
||||||
versionCode,
|
|
||||||
downloadUrl,
|
|
||||||
changelog
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
|
||||||
DisposableEffect(context) {
|
|
||||||
val receiver = object : BroadcastReceiver() {
|
|
||||||
@SuppressLint("Range")
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
|
||||||
if (intent?.action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
|
|
||||||
val id = intent.getLongExtra(
|
|
||||||
DownloadManager.EXTRA_DOWNLOAD_ID, -1
|
|
||||||
)
|
|
||||||
val query = DownloadManager.Query().setFilterById(id)
|
|
||||||
val downloadManager =
|
|
||||||
context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
|
||||||
val cursor = downloadManager.query(query)
|
|
||||||
if (cursor.moveToFirst()) {
|
|
||||||
val status = cursor.getInt(
|
|
||||||
cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
|
||||||
)
|
|
||||||
if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
|
||||||
val uri = cursor.getString(
|
|
||||||
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
|
|
||||||
)
|
|
||||||
onDownloaded(Uri.parse(uri))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ContextCompat.registerReceiver(
|
|
||||||
context,
|
|
||||||
receiver,
|
|
||||||
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
|
||||||
ContextCompat.RECEIVER_EXPORTED
|
|
||||||
)
|
|
||||||
onDispose {
|
|
||||||
context.unregisterReceiver(receiver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user