├── .github
└── workflows
│ └── android.yml
├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ └── yangfentuozi
│ │ └── runner
│ │ └── service
│ │ ├── IService.aidl
│ │ ├── callback
│ │ ├── IExecResultCallback.aidl
│ │ └── IInstallTermExtCallback.aidl
│ │ └── data
│ │ ├── CommandInfo.aidl
│ │ ├── EnvInfo.aidl
│ │ ├── ProcessInfo.aidl
│ │ └── TermExtVersion.aidl
│ ├── cpp
│ ├── CMakeLists.txt
│ ├── processutils.c
│ └── starter.c
│ ├── java
│ └── yangfentuozi
│ │ └── runner
│ │ ├── App.kt
│ │ ├── Runner.kt
│ │ ├── appservice
│ │ └── ExecOnBootService.kt
│ │ ├── base
│ │ ├── BaseActivity.kt
│ │ ├── BaseDialogBuilder.kt
│ │ └── BaseFragment.kt
│ │ ├── receiver
│ │ └── BootCompleteReceiver.kt
│ │ ├── service
│ │ ├── ServiceImpl.kt
│ │ ├── callback
│ │ │ ├── ExecResultCallback.kt
│ │ │ └── InstallTermExtCallback.kt
│ │ ├── data
│ │ │ ├── CommandInfo.kt
│ │ │ ├── EnvInfo.kt
│ │ │ ├── ProcessInfo.kt
│ │ │ └── TermExtVersion.kt
│ │ ├── database
│ │ │ ├── CommandDao.kt
│ │ │ ├── DataDbHelper.kt
│ │ │ └── EnvironmentDao.kt
│ │ ├── fakecontext
│ │ │ ├── FakeContext.kt
│ │ │ └── Workarounds.kt
│ │ └── util
│ │ │ ├── JniUtilsBase.kt
│ │ │ └── ProcessUtils.kt
│ │ ├── ui
│ │ ├── activity
│ │ │ ├── CrashReportActivity.kt
│ │ │ ├── InstallTermExtActivity.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── PackActivity.kt
│ │ │ └── envmanage
│ │ │ │ ├── EnvAdapter.kt
│ │ │ │ ├── EnvManageActivity.kt
│ │ │ │ └── ItemAdapter.kt
│ │ ├── dialog
│ │ │ ├── BlurBehindDialogBuilder.kt
│ │ │ └── ExecDialogBuilder.kt
│ │ └── fragment
│ │ │ ├── home
│ │ │ ├── GrantShizukuPermViewHolder.kt
│ │ │ ├── HomeAdapter.kt
│ │ │ ├── HomeFragment.kt
│ │ │ ├── ServiceStatusViewHolder.kt
│ │ │ ├── ShizukuStatusViewHolder.kt
│ │ │ └── TermExtStatusViewHolder.kt
│ │ │ ├── proc
│ │ │ ├── ProcAdapter.kt
│ │ │ └── ProcFragment.kt
│ │ │ ├── runner
│ │ │ ├── CommandAdapter.kt
│ │ │ └── RunnerFragment.kt
│ │ │ ├── settings
│ │ │ └── SettingsFragment.kt
│ │ │ └── terminal
│ │ │ └── TerminalFragment.kt
│ │ └── util
│ │ ├── ThemeUtil.kt
│ │ ├── ThrowableUtil.kt
│ │ └── UpdateUtil.kt
│ └── res
│ ├── color-night
│ └── home_card_background_color.xml
│ ├── color
│ └── home_card_background_color.xml
│ ├── drawable
│ ├── ic_add_24.xml
│ ├── ic_backup_restore_24.xml
│ ├── ic_check_circle_outline_24.xml
│ ├── ic_close_24.xml
│ ├── ic_configs_24.xml
│ ├── ic_dark_mode_24.xml
│ ├── ic_delete_outline_24.xml
│ ├── ic_empty_icon_24.xml
│ ├── ic_error_outline_24.xml
│ ├── ic_file_24.xml
│ ├── ic_format_color_fill_24.xml
│ ├── ic_help_24.xml
│ ├── ic_home.xml
│ ├── ic_home_baseline_24.xml
│ ├── ic_home_outline_24.xml
│ ├── ic_import_export_24.xml
│ ├── ic_info_24.xml
│ ├── ic_invert_colors_24.xml
│ ├── ic_language_24.xml
│ ├── ic_launcher_background.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_launcher_round.xml
│ ├── ic_layers.xml
│ ├── ic_layers_baseline_24.xml
│ ├── ic_layers_outline_24.xml
│ ├── ic_merge_type_24.xml
│ ├── ic_palette_outline_24.xml
│ ├── ic_play_arrow.xml
│ ├── ic_play_arrow_baseline_24.xml
│ ├── ic_play_arrow_outline_24.xml
│ ├── ic_restore_24.xml
│ ├── ic_run_24.xml
│ ├── ic_settings.xml
│ ├── ic_settings_applications_outline_24.xml
│ ├── ic_settings_baseline_24.xml
│ ├── ic_settings_outline_24.xml
│ ├── ic_stop_circle_outline_24.xml
│ ├── ic_terminal_24.xml
│ └── shape_circle_icon_background.xml
│ ├── layout
│ ├── activity_crash_report.xml
│ ├── activity_env_manage.xml
│ ├── activity_main.xml
│ ├── activity_pack.xml
│ ├── activity_stream_activity.xml
│ ├── dialog_about.xml
│ ├── dialog_add.xml
│ ├── dialog_edit.xml
│ ├── dialog_edit_env.xml
│ ├── dialog_edit_env_e.xml
│ ├── dialog_edit_env_e2.xml
│ ├── dialog_exec.xml
│ ├── dialog_import.xml
│ ├── dialog_pick_backup.xml
│ ├── fragment_home.xml
│ ├── fragment_proc.xml
│ ├── fragment_runner.xml
│ ├── fragment_settings.xml
│ ├── fragment_terminal.xml
│ ├── home_item_container.xml
│ ├── home_service_status.xml
│ ├── home_shizuku_perm_request.xml
│ ├── home_shizuku_status.xml
│ ├── home_term_ext_status.xml
│ ├── item_cmd.xml
│ ├── item_env.xml
│ ├── item_env_item.xml
│ └── item_proc.xml
│ ├── menu
│ ├── bottom_nav_menu.xml
│ └── menu_home.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── navigation
│ └── mobile_navigation.xml
│ ├── resources.properties
│ ├── values-land
│ └── dimens.xml
│ ├── values-night
│ └── styles.xml
│ ├── values-sw600dp
│ ├── dimens.xml
│ └── values.xml
│ ├── values-w1240dp
│ └── dimens.xml
│ ├── values-w600dp
│ └── dimens.xml
│ ├── values-zh-rCN
│ └── strings.xml
│ ├── values
│ ├── arrays.xml
│ ├── attrs.xml
│ ├── dimens.xml
│ ├── strings.xml
│ ├── styles.xml
│ ├── theme.xml
│ ├── themes_custom.xml
│ ├── themes_overlay.xml
│ ├── themes_override.xml
│ └── values.xml
│ └── xml
│ ├── backup_rules.xml
│ ├── data_extraction_rules.xml
│ └── preference_setting.xml
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: App
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | paths-ignore:
7 | - '.github/ISSUE_TEMPLATE'
8 | workflow_dispatch:
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-24.04
13 | if: ${{ !startsWith(github.event.head_commit.message, '[skip ci]') && github.repository_owner == 'yangFenTuoZi' }}
14 |
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 | with:
19 | submodules: 'recursive'
20 | fetch-depth: 0
21 |
22 | - name: Setup Java
23 | uses: actions/setup-java@v4
24 | with:
25 | distribution: 'temurin'
26 | java-version: '21'
27 | cache: 'gradle'
28 |
29 | - name: Write key
30 | if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
31 | run: |
32 | touch signing.properties
33 | echo KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }} >> signing.properties
34 | echo KEYSTORE_ALIAS=${{ secrets.KEYSTORE_ALIAS }} >> signing.properties
35 | echo KEYSTORE_ALIAS_PASSWORD='${{ secrets.KEYSTORE_ALIAS_PASSWORD }}' >> signing.properties
36 | echo KEYSTORE_FILE=../key.jks >> signing.properties
37 | echo '${{ secrets.KEYSTORE }}' | base64 --decode > key.jks
38 |
39 | - name: Write temporary key
40 | if: hashFiles('key.jks') == '' && !steps.vars.outputs.HAS_SECRET
41 | run: |
42 | keytool -genkey -alias a -dname CN=_ -storepass passwd -keypass passwd -keystore key.jks
43 | echo KEYSTORE_PASSWORD=passwd >> signing.properties
44 | echo KEYSTORE_ALIAS=a >> signing.properties
45 | echo KEYSTORE_ALIAS_PASSWORD=passwd >> signing.properties
46 | echo KEYSTORE_FILE=../key.jks >> signing.properties
47 |
48 | - name: Cache Gradle Dependencies
49 | uses: actions/cache@v4
50 | with:
51 | path: |
52 | ~/.gradle/caches
53 | ~/.gradle/wrapper
54 | !~/.gradle/caches/build-cache-*
55 | key: gradle-deps-app-${{ hashFiles('**/build.gradle') }}
56 | restore-keys: |
57 | gradle-deps
58 |
59 | - name: Cache Gradle Build
60 | uses: actions/cache@v4
61 | with:
62 | path: |
63 | ~/.gradle/caches/build-cache-*
64 | key: gradle-builds-app-${{ github.sha }}
65 | restore-keys: |
66 | gradle-builds
67 |
68 | - name: Build with Gradle
69 | id: buildWithGradle
70 | run: |
71 | echo 'org.gradle.caching=true' >> gradle.properties
72 | echo 'org.gradle.parallel=true' >> gradle.properties
73 | echo 'org.gradle.vfs.watch=true' >> gradle.properties
74 | echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
75 | chmod +x gradlew
76 | ./gradlew :app:assembleRelease :app:assembleDebug
77 | releaseName=`ls app/build/outputs/apk/release/runner*-v*-release.apk | awk -F '(/|.apk)' '{print $6}'` && echo "releaseName=$releaseName" >> $GITHUB_OUTPUT
78 | debugName=`ls app/build/outputs/apk/debug/runner*-v*-debug.apk | awk -F '(/|.apk)' '{print $6}'` && echo "debugName=$debugName" >> $GITHUB_OUTPUT
79 |
80 | - name: Upload release
81 | if: success()
82 | uses: actions/upload-artifact@v4
83 | with:
84 | name: ${{ steps.buildWithGradle.outputs.releaseName }}
85 | path: "app/build/outputs/apk/release/*.apk"
86 |
87 | - name: Upload debug
88 | if: success()
89 | uses: actions/upload-artifact@v4
90 | with:
91 | name: ${{ steps.buildWithGradle.outputs.debugName }}
92 | path: "app/build/outputs/apk/debug/*.apk"
93 |
94 | - name: Upload mappings
95 | uses: actions/upload-artifact@v4
96 | with:
97 | name: mappings
98 | path: "app/build/outputs/mapping/release"
99 |
100 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # 项目排除路径
2 | # Gradle files
3 | .gradle/
4 | build/
5 |
6 | # Local configuration file (sdk path, etc)
7 | local.properties
8 |
9 | # Log/OS Files
10 | *.log
11 |
12 | # Android Studio generated files and folders
13 | captures/
14 | .externalNativeBuild/
15 | .cxx/
16 | *.apk
17 | output.json
18 |
19 | # IntelliJ
20 | *.iml
21 | .idea/
22 | misc.xml
23 | deploymentTargetDropDown.xml
24 | render.experimental.xml
25 |
26 | # Keystore files
27 | *.jks
28 | *.keystore
29 |
30 | # Google Services (e.g. APIs or Firebase)
31 | google-services.json
32 |
33 | # Android Profiling
34 | *.hprof
35 |
36 | /signing.properties
37 | /out
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Runner
2 |
3 | 本APP受到[ShizukuRunner](https://github.com/WuDi-ZhanShen/ShizukuRunner)的启发而写,引用~~部分~~
4 | 极小部分[ShizukuRunner](https://github.com/WuDi-ZhanShen/ShizukuRunner)的代码
5 | 目前仍在开发,大部分功能不全,请等后续
6 | 如果喜欢这个项目的话请多多为我Star吧
7 |
8 | ## 下载
9 |
10 | [Github](https://github.com/yangFenTuoZi/Runner/releases)
11 |
12 | ## 计划
13 |
14 | **开发大致按以下顺序进行 此表随时变动 仅供参考**
15 |
16 | **暂并不考虑支持其他语言 如有需要 请提 [PR](https://github.com/yangFenTuoZi/Runner/pulls)**
17 |
18 | ### 功能
19 |
20 | - [x] 命令执行实时输出
21 | - [x] 分离终端扩展包
22 | - [x] 终端bash, busybox
23 | - [x] 允许进程后台运行
24 | - [x] 允许执行前降低权限(可能仅 Root 可用)
25 | - [x] 支持强杀进程
26 | - [x] 支持杀所有子进程
27 | - [x] 增加进程管理
28 | - [x] 支持自定义环境变量
29 | - [x] 开机自启动执行命令
30 | - [x] App数据备份与还原
31 | - [ ] 将命令打包为独立 APK
32 | - [ ] 终端
33 | - [ ] 终端侧载模块(类似 dpkg )
34 | - [ ] 外部CLI
35 |
36 | ### 语言
37 |
38 | - [x] 中文
39 | - [x] 英文
40 |
41 | ## 反馈注意事项
42 |
43 | 1. 反馈请说明设备信息(系统版本、UI版本、权限等)
44 | 2. 反馈请附日志
45 | 3. 请使用**中文标题**反馈
46 |
47 | ## 注意
48 |
49 | 2024/10/04: **不再使用Shizuku**
50 |
51 | 2025/04/21: **重新使用Shizuku**
52 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # 项目排除路径
2 | # Gradle files
3 | .gradle/
4 | build/
5 |
6 | # Local configuration file (sdk path, etc)
7 | local.properties
8 |
9 | # Log/OS Files
10 | *.log
11 |
12 | # Android Studio generated files and folders
13 | captures/
14 | .externalNativeBuild/
15 | .cxx/
16 | *.apk
17 | output.json
18 |
19 | # IntelliJ
20 | *.iml
21 | .idea/
22 | misc.xml
23 | deploymentTargetDropDown.xml
24 | render.experimental.xml
25 |
26 | # Keystore files
27 | *.jks
28 | *.keystore
29 |
30 | # Google Services (e.g. APIs or Firebase)
31 | google-services.json
32 |
33 | # Android Profiling
34 | *.hprof
35 |
36 | release
37 | debug
38 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.风堇
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -dontobfuscate
23 | -dontwarn org.jetbrains.annotations.NotNull
24 | -dontwarn org.jetbrains.annotations.Nullable
25 | -keep class yangfentuozi.runner.service.** { *; }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
36 |
37 |
41 |
42 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
59 |
60 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/app/src/main/aidl/yangfentuozi/runner/service/IService.aidl:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service;
2 |
3 | import yangfentuozi.runner.service.data.CommandInfo;
4 | import yangfentuozi.runner.service.data.TermExtVersion;
5 | import yangfentuozi.runner.service.data.ProcessInfo;
6 | import yangfentuozi.runner.service.data.EnvInfo;
7 |
8 | import yangfentuozi.runner.service.callback.IExecResultCallback;
9 | import yangfentuozi.runner.service.callback.IInstallTermExtCallback;
10 |
11 | interface IService {
12 | void destroy() = 16777114;
13 | void exit() = 1;
14 | int version() = 2;
15 |
16 | void exec(String command, String ids, String procName, in IExecResultCallback callback) = 100;
17 |
18 | int size() = 200;
19 | CommandInfo read(int position) = 201;
20 | CommandInfo[] readAll() = 202;
21 | void delete(int position) = 203;
22 | void edit(in CommandInfo cmdInfo, int position) = 204;
23 | void insert(in CommandInfo cmdInfo) = 205;
24 | void move(int position, int afterPosition) = 206;
25 | void insertInto(in CommandInfo cmdInfo, int position) = 207;
26 |
27 | void deleteEnv(String key) = 300;
28 | boolean insertEnv(String key, String value) = 301;
29 | boolean updateEnv(in EnvInfo from, in EnvInfo to) = 302;
30 | String getEnv(String key) = 303;
31 | EnvInfo[] getAllEnv() = 304;
32 |
33 | ProcessInfo[] getProcesses() = 400;
34 | boolean[] sendSignal(in int[] pid, int signal) = 401;
35 |
36 | void backupData(String output, boolean data, boolean termHome, boolean termUsr) = 500;
37 | void restoreData(String input) = 501;
38 |
39 | void installTermExt(String termExtZip, in IInstallTermExtCallback callback) = 1000;
40 | void removeTermExt() = 1001;
41 | TermExtVersion getTermExtVersion() = 1002;
42 | }
--------------------------------------------------------------------------------
/app/src/main/aidl/yangfentuozi/runner/service/callback/IExecResultCallback.aidl:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.callback;
2 |
3 | interface IExecResultCallback {
4 | void onOutput(String outputs);
5 | void onExit(int exitValue);
6 | }
--------------------------------------------------------------------------------
/app/src/main/aidl/yangfentuozi/runner/service/callback/IInstallTermExtCallback.aidl:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.callback;
2 |
3 | interface IInstallTermExtCallback {
4 | void onMessage(String message);
5 | void onExit(boolean isSuccessful);
6 | }
--------------------------------------------------------------------------------
/app/src/main/aidl/yangfentuozi/runner/service/data/CommandInfo.aidl:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.data;
2 | parcelable CommandInfo;
--------------------------------------------------------------------------------
/app/src/main/aidl/yangfentuozi/runner/service/data/EnvInfo.aidl:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.data;
2 | parcelable EnvInfo;
--------------------------------------------------------------------------------
/app/src/main/aidl/yangfentuozi/runner/service/data/ProcessInfo.aidl:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.data;
2 | parcelable ProcessInfo;
--------------------------------------------------------------------------------
/app/src/main/aidl/yangfentuozi/runner/service/data/TermExtVersion.aidl:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.data;
2 | parcelable TermExtVersion;
--------------------------------------------------------------------------------
/app/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.22.1)
2 |
3 | project("runner")
4 |
5 | add_executable(starter
6 | starter.c)
7 |
8 | add_library(processutils SHARED
9 | processutils.c)
10 |
11 | set_target_properties(starter PROPERTIES PREFIX "lib")
12 | set_target_properties(starter PROPERTIES SUFFIX ".so")
13 |
14 | set_target_properties(processutils PROPERTIES PREFIX "lib")
15 | set_target_properties(processutils PROPERTIES SUFFIX ".so")
16 |
--------------------------------------------------------------------------------
/app/src/main/cpp/starter.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | typedef struct {
14 | uid_t uid;
15 | gid_t gid;
16 | gid_t *groups;
17 | size_t groups_count;
18 | } UserInfo;
19 |
20 | // Constants for fixed command execution
21 | static const char *BASH_PATH = "/data/local/tmp/runner/usr/bin/bash";
22 |
23 | void parse_uid_gid(const char *arg, UserInfo *info) {
24 | // Initialize structure
25 | memset(info, 0, sizeof(UserInfo));
26 |
27 | if (!arg || !*arg) return;
28 |
29 | char *str = strdup(arg);
30 | if (!str) {
31 | perror("strdup failed");
32 | return;
33 | }
34 |
35 | // First token - UID
36 | char *token = strtok(str, ",");
37 | if (token) info->uid = (uid_t) atoi(token);
38 |
39 | // Second token - GID
40 | token = strtok(NULL, ",");
41 | if (token) info->gid = (gid_t) atoi(token);
42 |
43 | // Count additional groups
44 | size_t count = 0;
45 | char *tmp = str;
46 | while ((tmp = strchr(tmp, ','))) {
47 | count++;
48 | tmp++;
49 | }
50 | count = count > 2 ? count - 2 : 0;
51 |
52 | if (count > 0) {
53 | info->groups = calloc(count, sizeof(gid_t));
54 | if (!info->groups) {
55 | perror("malloc failed");
56 | free(str);
57 | return;
58 | }
59 |
60 | // Parse groups
61 | for (size_t i = 0; i < count; i++) {
62 | token = strtok(NULL, ",");
63 | if (token) info->groups[i] = (gid_t) atoi(token);
64 | }
65 | info->groups_count = count;
66 | }
67 |
68 | free(str);
69 | }
70 |
71 | int set_user_groups(const UserInfo *info) {
72 | if (info->gid != 0 && setgid(info->gid) != 0) {
73 | perror("setgid failed");
74 | return 0;
75 | }
76 |
77 | if (info->groups_count > 0 && setgroups(info->groups_count, info->groups) != 0) {
78 | perror("setgroups failed");
79 | return 0;
80 | }
81 |
82 | if (info->uid != 0 && setuid(info->uid) != 0) {
83 | perror("setuid failed");
84 | return 0;
85 | }
86 |
87 | return 1;
88 | }
89 |
90 | void free_user_info(UserInfo *info) {
91 | if (info) {
92 | free(info->groups);
93 | info->groups = NULL;
94 | info->groups_count = 0;
95 | }
96 | }
97 |
98 | int main(int argc, char *argv[]) {
99 | if (argc != 3) {
100 | fprintf(stderr, "Usage: %s \n", argv[0]);
101 | return EXIT_FAILURE;
102 | }
103 | UserInfo user_info = {0};
104 |
105 | // Parse UID/GID argument
106 | if (isdigit(argv[1][0]) && strcmp(argv[1], "-1") != 0) {
107 | parse_uid_gid(argv[1], &user_info);
108 | }
109 |
110 | // Set user and group IDs if specified
111 | if ((user_info.uid != 0 || user_info.gid != 0 || user_info.groups_count > 0) &&
112 | !set_user_groups(&user_info)) {
113 | free_user_info(&user_info);
114 | return EXIT_FAILURE;
115 | }
116 |
117 | free_user_info(&user_info);
118 |
119 | char *const args[] = {"bash", "--nice-name", argv[2], NULL};
120 | return execvp(BASH_PATH, args);
121 | }
122 |
123 |
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/App.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.content.Intent
6 | import android.content.SharedPreferences
7 | import android.os.Environment
8 | import android.os.Looper
9 | import androidx.appcompat.app.AppCompatDelegate
10 | import androidx.preference.PreferenceManager
11 | import yangfentuozi.runner.ui.activity.CrashReportActivity
12 | import yangfentuozi.runner.util.ThemeUtil
13 | import java.io.File
14 | import java.util.LinkedList
15 |
16 |
17 | class App : Application(), Thread.UncaughtExceptionHandler {
18 | private val activities: MutableList = LinkedList()
19 | private lateinit var pref: SharedPreferences
20 |
21 | override fun onCreate() {
22 | super.onCreate()
23 | instance = this
24 | pref = PreferenceManager.getDefaultSharedPreferences(this)
25 | AppCompatDelegate.setDefaultNightMode(ThemeUtil.darkTheme)
26 |
27 | Runner.init()
28 | Thread.setDefaultUncaughtExceptionHandler(this)
29 | }
30 |
31 | fun addActivity(activity: Activity?) {
32 | activities.add(activity!!)
33 | }
34 |
35 | fun removeActivity(activity: Activity?) {
36 | activities.remove(activity)
37 | }
38 |
39 | fun finishApp() {
40 | for (activity in activities) activity.finish()
41 | activities.clear()
42 | }
43 |
44 | override fun onTerminate() {
45 | super.onTerminate()
46 | Runner.remove()
47 | }
48 |
49 | private fun crashHandler(t: Thread, e: Throwable) {
50 | val fileName = "runnerCrash-" + System.currentTimeMillis() + ".log"
51 | val file: File?
52 | if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
53 | val dir =
54 | File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS)
55 | if (!dir.exists()) {
56 | dir.mkdirs()
57 | }
58 | file = File(dir, fileName)
59 | } else {
60 | file = getExternalFilesDir(fileName)
61 | }
62 | checkNotNull(file)
63 |
64 | startActivity(
65 | Intent(this, CrashReportActivity::class.java)
66 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
67 | .putExtra("crash_info", e.stackTraceToString())
68 | .putExtra("crash_file", file.absolutePath)
69 | )
70 | }
71 |
72 | override fun uncaughtException(t: Thread, e: Throwable) {
73 | Thread {
74 | Looper.prepare()
75 | crashHandler(t, e)
76 | Looper.loop()
77 | }.start()
78 | }
79 |
80 | companion object {
81 | lateinit var instance: App
82 | private set
83 |
84 | val preferences: SharedPreferences
85 | get() = instance.pref
86 | }
87 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/appservice/ExecOnBootService.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.appservice
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import android.os.Handler
6 | import android.os.IBinder
7 | import android.os.Looper
8 | import android.util.Log
9 | import yangfentuozi.runner.App
10 | import yangfentuozi.runner.Runner
11 | import yangfentuozi.runner.service.callback.IExecResultCallback
12 | import java.util.concurrent.ExecutorService
13 | import java.util.concurrent.Executors
14 |
15 | class ExecOnBootService : Service() {
16 |
17 | companion object {
18 | const val TAG = "ExecOnBootService"
19 | var isRunning = false
20 | }
21 |
22 | private lateinit var executor: ExecutorService
23 |
24 | val mHandler = Handler(Looper.getMainLooper())
25 |
26 | val timeout: Long = 20_000L
27 |
28 | override fun onCreate() {
29 | super.onCreate()
30 | Log.i(TAG, "service created")
31 | executor = Executors.newFixedThreadPool(1)
32 | }
33 |
34 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
35 | Log.i(TAG, "service starting command")
36 | isRunning = true
37 |
38 | executor.execute {
39 | Runner.refreshStatus()
40 | if (Runner.waitShizuku(timeout)) {
41 | Log.i(TAG, "Shizuku is ready")
42 | Runner.refreshStatus()
43 | Runner.tryBindService()
44 | if (Runner.waitService((timeout * 3).toLong())) {
45 | Log.i(TAG, "Service is ready")
46 | exec()
47 | } else {
48 | Log.i(TAG, "Service is not ready")
49 | mHandler.post { stopSelf() }
50 | }
51 | } else {
52 | Log.i(TAG, "Shizuku is not ready")
53 | mHandler.post { stopSelf() }
54 | }
55 | }
56 |
57 | return START_STICKY
58 | }
59 |
60 | fun exec() {
61 | val sharedPreferences = App.instance.getSharedPreferences("startup_script", MODE_PRIVATE)
62 | val command = sharedPreferences.getString("startup_script_command", "")
63 | val targetPerm = if (sharedPreferences.getBoolean("startup_script_reduce_perm", false)) sharedPreferences.getString("startup_script_target_perm", null) else null
64 | if (command.isNullOrEmpty()) {
65 | Log.i(TAG, "exec: command is empty")
66 | return
67 | }
68 | Log.i(TAG, "exec: $command")
69 | Runner.service?.exec(
70 | command,
71 | targetPerm ?: "",
72 | "ExecOnBootTask",
73 | object : IExecResultCallback.Stub() {
74 | override fun onOutput(outputs: String?) {
75 | Log.i(TAG, "onOutput: $outputs")
76 | }
77 |
78 | override fun onExit(exitValue: Int) {
79 | Log.i(TAG, "exit with $exitValue")
80 | stopSelf()
81 | }
82 | })
83 | }
84 |
85 | override fun onDestroy() {
86 | super.onDestroy()
87 | isRunning = false
88 | executor.shutdownNow()
89 | super.onDestroy()
90 | }
91 |
92 | override fun onBind(intent: Intent?): IBinder? = null
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.base
2 |
3 | import android.content.res.Resources
4 | import android.graphics.Color
5 | import android.os.Bundle
6 | import android.os.Handler
7 | import android.os.Looper
8 | import rikka.material.app.MaterialActivity
9 | import yangfentuozi.runner.App
10 | import yangfentuozi.runner.R
11 | import yangfentuozi.runner.util.ThemeUtil
12 |
13 |
14 | open class BaseActivity : MaterialActivity() {
15 |
16 | private val mHandler: Handler = Handler(Looper.getMainLooper())
17 | private val mUiThread: Thread = Thread.currentThread()
18 | var isDialogShowing: Boolean = false
19 |
20 | lateinit var mApp: App
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | mApp = application as App
24 | mApp.addActivity(this)
25 | setTheme(R.style.AppTheme);
26 | super.onCreate(savedInstanceState)
27 | }
28 |
29 | override fun onDestroy() {
30 | mApp.removeActivity(this)
31 | super.onDestroy()
32 | }
33 |
34 | override fun onApplyUserThemeResource(theme: Resources.Theme, isDecorView: Boolean) {
35 | if (!ThemeUtil.isSystemAccent) {
36 | theme.applyStyle(ThemeUtil.colorThemeStyleRes, true);
37 | }
38 | theme.applyStyle(ThemeUtil.getNightThemeStyleRes(this), true);
39 | theme.applyStyle(rikka.material.preference.R.style.ThemeOverlay_Rikka_Material3_Preference, true)
40 | }
41 |
42 | override fun computeUserThemeKey(): String? {
43 | return ThemeUtil.colorTheme + ThemeUtil.getNightTheme(this)
44 | }
45 |
46 | override fun onApplyTranslucentSystemBars() {
47 | super.onApplyTranslucentSystemBars()
48 |
49 | // 设置状态栏导航栏透明
50 | val window = window
51 | window.statusBarColor = Color.TRANSPARENT
52 | window.navigationBarColor = Color.TRANSPARENT
53 | }
54 |
55 | fun runOnMainThread(action: Runnable) {
56 | if (Thread.currentThread() !== mUiThread) {
57 | mHandler.post(action)
58 | } else {
59 | action.run()
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/base/BaseDialogBuilder.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.base
2 |
3 | import android.content.DialogInterface
4 | import androidx.annotation.StringRes
5 | import androidx.appcompat.app.AlertDialog
6 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
7 |
8 | open class BaseDialogBuilder @Throws(DialogShowingException::class) constructor(context: BaseActivity) : MaterialAlertDialogBuilder(context) {
9 | class DialogShowingException : Exception()
10 |
11 | private val mBaseActivity: BaseActivity = context
12 | private var mAlertDialog: AlertDialog? = null
13 | private var mOnDismissListener: DialogInterface.OnDismissListener? = null
14 |
15 | init {
16 | if (mBaseActivity.isDialogShowing) throw DialogShowingException()
17 | mBaseActivity.isDialogShowing = true
18 | super.setOnDismissListener { dialogInterface ->
19 | mBaseActivity.isDialogShowing = false
20 | mOnDismissListener?.onDismiss(dialogInterface)
21 | }
22 | }
23 |
24 | fun getAlertDialog(): AlertDialog? = mAlertDialog
25 |
26 |
27 | override fun create(): AlertDialog {
28 | return super.create().also { mAlertDialog = it }
29 | }
30 |
31 |
32 | override fun setOnDismissListener(onDismissListener: DialogInterface.OnDismissListener?): MaterialAlertDialogBuilder {
33 | mOnDismissListener = onDismissListener
34 | return this
35 | }
36 |
37 | fun runOnMainThread(action: Runnable) {
38 | mBaseActivity.runOnMainThread(action)
39 | }
40 |
41 |
42 | fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
43 | return mBaseActivity.getString(resId, *formatArgs)
44 | }
45 |
46 |
47 | fun getString(@StringRes resId: Int): String {
48 | return mBaseActivity.getString(resId)
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/base/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.base
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import yangfentuozi.runner.ui.activity.MainActivity
6 |
7 | open class BaseFragment : Fragment() {
8 | protected lateinit var mMainActivity: MainActivity
9 | val appBar get() = mMainActivity.appBar
10 | val toolbar get() = mMainActivity.toolbar
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | mMainActivity = (activity as? MainActivity)
15 | ?: throw RuntimeException("父Activity非MainActivity")
16 | }
17 |
18 | override fun onStart() {
19 | super.onStart()
20 | mMainActivity.toolbar.subtitle = null
21 | }
22 |
23 | fun runOnMainThread(action: Runnable) {
24 | mMainActivity.runOnMainThread(action)
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/receiver/BootCompleteReceiver.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.receiver
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Process
7 | import android.util.Log
8 | import yangfentuozi.runner.App
9 | import yangfentuozi.runner.appservice.ExecOnBootService
10 |
11 |
12 | class BootCompleteReceiver : BroadcastReceiver() {
13 | companion object {
14 | const val TAG = "BootCompleteReceiver"
15 | }
16 |
17 | override fun onReceive(context: Context, intent: Intent) {
18 | if (Intent.ACTION_LOCKED_BOOT_COMPLETED != intent.action && Intent.ACTION_BOOT_COMPLETED != intent.action) {
19 | return
20 | }
21 |
22 | if (!App.preferences.getBoolean("auto_start_exec", false)) return
23 | if (Process.myUid() / 100000 > 0) return
24 |
25 | Log.i(TAG, "receive $intent")
26 |
27 | if (ExecOnBootService.isRunning) {
28 | Log.i(TAG, "ExecOnBootService is already running")
29 | return
30 | }
31 | Log.i(TAG, "start ExecOnBootService")
32 | context.startService(Intent(context, ExecOnBootService::class.java))
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/callback/ExecResultCallback.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.callback
2 |
3 | import android.os.RemoteException
4 |
5 | class ExecResultCallback(private val mCallback: IExecResultCallback?) {
6 | fun onOutput(outputs: String?) {
7 | try {
8 | mCallback?.onOutput(outputs)
9 | } catch (_: RemoteException) {
10 | }
11 | }
12 |
13 | fun onExit(exitValue: Int) {
14 | try {
15 | mCallback?.onExit(exitValue)
16 | } catch (_: RemoteException) {
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/callback/InstallTermExtCallback.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.callback
2 |
3 | import android.os.RemoteException
4 |
5 | class InstallTermExtCallback(private val mCallback: IInstallTermExtCallback?) {
6 | fun onMessage(message: String?) {
7 | try {
8 | mCallback?.onMessage(message)
9 | } catch (_: RemoteException) {
10 | }
11 | }
12 |
13 | fun onExit(isSuccessful: Boolean) {
14 | try {
15 | mCallback?.onExit(isSuccessful)
16 | } catch (_: RemoteException) {
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/data/CommandInfo.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.data
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 |
6 | open class CommandInfo : Parcelable {
7 | var name: String? = null
8 | var command: String? = null
9 | var keepAlive: Boolean = false
10 | var reducePerm: Boolean = false
11 | var targetPerm: String? = null
12 |
13 | constructor()
14 |
15 | constructor(source: Parcel) : super() {
16 | name = source.readString()
17 | command = source.readString()
18 | keepAlive = source.readInt() == 1
19 | reducePerm = source.readInt() == 1
20 | targetPerm = source.readString()
21 | }
22 |
23 | override fun describeContents(): Int {
24 | return 0
25 | }
26 |
27 | override fun writeToParcel(dest: Parcel, flags: Int) {
28 | dest.writeString(name)
29 | dest.writeString(command)
30 | dest.writeInt(if (keepAlive) 1 else 0)
31 | dest.writeInt(if (reducePerm) 1 else 0)
32 | dest.writeString(targetPerm)
33 | }
34 |
35 | companion object {
36 | @JvmField
37 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator {
38 | override fun createFromParcel(source: Parcel): CommandInfo {
39 | return CommandInfo(source)
40 | }
41 |
42 | override fun newArray(size: Int): Array {
43 | return arrayOfNulls(size)
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/data/EnvInfo.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.data
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 |
6 | open class EnvInfo : Parcelable {
7 | var key: String? = null
8 | var value: String? = null
9 |
10 | constructor()
11 |
12 | constructor(source: Parcel) : super() {
13 | key = source.readString()
14 | value = source.readString()
15 | }
16 |
17 | override fun describeContents(): Int {
18 | return 0
19 | }
20 |
21 | override fun writeToParcel(dest: Parcel, flags: Int) {
22 | dest.writeString(key)
23 | dest.writeString(value)
24 | }
25 |
26 | companion object {
27 | @JvmField
28 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator {
29 | override fun createFromParcel(source: Parcel): EnvInfo {
30 | return EnvInfo(source)
31 | }
32 |
33 | override fun newArray(size: Int): Array {
34 | return arrayOfNulls(size)
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/data/ProcessInfo.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.data
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 | import java.util.Objects
6 |
7 | open class ProcessInfo : Parcelable {
8 | var pid: Int = 0
9 | var ppid: Int = 0
10 | var exe: String? = null
11 | var args: Array? = null
12 |
13 | constructor()
14 |
15 | constructor(source: Parcel) : super() {
16 | pid = source.readInt()
17 | ppid = source.readInt()
18 | exe = source.readString()
19 | val argsLength = source.readInt()
20 | args = arrayOfNulls(argsLength)
21 | source.readStringArray(args!!)
22 | }
23 |
24 | override fun describeContents(): Int {
25 | return 0
26 | }
27 |
28 | override fun writeToParcel(dest: Parcel, flags: Int) {
29 | dest.writeInt(pid)
30 | dest.writeInt(ppid)
31 | dest.writeString(exe)
32 | dest.writeInt(args?.size ?: 0)
33 | dest.writeStringArray(args)
34 | }
35 |
36 | override fun toString(): String {
37 | return "ProcessInfo{" +
38 | "pid=" + pid +
39 | ", ppid=" + ppid +
40 | ", name='" + exe + '\'' +
41 | ", args=" + args.contentToString() +
42 | '}'
43 | }
44 |
45 | override fun equals(o: Any?): Boolean {
46 | if (o !is ProcessInfo) return false
47 | return pid == o.pid
48 | }
49 |
50 | override fun hashCode(): Int {
51 | return Objects.hashCode(pid)
52 | }
53 |
54 | companion object {
55 | @JvmField
56 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator {
57 | override fun createFromParcel(source: Parcel): ProcessInfo {
58 | return ProcessInfo(source)
59 | }
60 |
61 | override fun newArray(size: Int): Array {
62 | return arrayOfNulls(size)
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/data/TermExtVersion.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.data
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 | import java.io.InputStream
6 | import java.util.Properties
7 |
8 | open class TermExtVersion : Parcelable {
9 | val versionName: String?
10 | val versionCode: Int
11 | val abi: String?
12 |
13 | constructor(versionName: String?, versionCode: Int, abi: String?) {
14 | this.versionName = versionName
15 | this.versionCode = versionCode
16 | this.abi = abi
17 | }
18 |
19 | constructor(`in`: InputStream?) {
20 | var versionCode1: Int
21 | val buildProp = Properties()
22 | buildProp.load(`in`)
23 | versionName = buildProp.getProperty("version.name")
24 | try {
25 | versionCode1 = buildProp.getProperty("version.code").toInt()
26 | } catch (e: NumberFormatException) {
27 | versionCode1 = -1
28 | }
29 | versionCode = versionCode1
30 | abi = buildProp.getProperty("build.abi")
31 | }
32 |
33 | override fun describeContents(): Int {
34 | return 0
35 | }
36 |
37 | override fun writeToParcel(dest: Parcel, flags: Int) {
38 | dest.writeString(this.versionName)
39 | dest.writeInt(this.versionCode)
40 | dest.writeString(this.abi)
41 | }
42 |
43 | protected constructor(`in`: Parcel) {
44 | this.versionName = `in`.readString()
45 | this.versionCode = `in`.readInt()
46 | this.abi = `in`.readString()
47 | }
48 |
49 | companion object {
50 | @JvmField
51 | val CREATOR: Parcelable.Creator =
52 | object : Parcelable.Creator {
53 | override fun createFromParcel(source: Parcel): TermExtVersion {
54 | return TermExtVersion(source)
55 | }
56 |
57 | override fun newArray(size: Int): Array {
58 | return arrayOfNulls(size)
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/database/DataDbHelper.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.database
2 |
3 | import android.content.Context
4 | import android.database.sqlite.SQLiteDatabase
5 | import android.database.sqlite.SQLiteOpenHelper
6 | import yangfentuozi.runner.service.ServiceImpl
7 |
8 | class DataDbHelper(context: Context?) :
9 | SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
10 | override fun onCreate(db: SQLiteDatabase) {
11 | db.execSQL(TABLE_COMMANDS_CREATE)
12 | db.execSQL(TABLE_ENVIRONMENT_CREATE)
13 | }
14 |
15 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
16 | db.execSQL("DROP TABLE IF EXISTS $TABLE_COMMANDS")
17 | db.execSQL("DROP TABLE IF EXISTS $TABLE_ENVIRONMENT")
18 | onCreate(db)
19 | }
20 |
21 | val database: SQLiteDatabase = writableDatabase
22 |
23 | companion object {
24 | const val DATABASE_NAME: String = ServiceImpl.DATA_PATH + "/data.db"
25 | const val DATABASE_VERSION: Int = 1
26 |
27 | // commands 表
28 | const val TABLE_COMMANDS: String = "commands"
29 | const val COLUMN_ID: String = "_id"
30 | const val COLUMN_POSITION: String = "position"
31 | const val COLUMN_NAME: String = "name"
32 | const val COLUMN_COMMAND: String = "command"
33 | const val COLUMN_KEEP_ALIVE: String = "keep_alive"
34 | const val COLUMN_REDUCE_PERM: String = "reduce_perm"
35 | const val COLUMN_TARGET_PERM: String = "target_perm"
36 |
37 | private const val TABLE_COMMANDS_CREATE = "CREATE TABLE " + TABLE_COMMANDS + " (" +
38 | COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
39 | COLUMN_POSITION + " INTEGER NOT NULL, " +
40 | COLUMN_NAME + " TEXT NOT NULL, " +
41 | COLUMN_COMMAND + " TEXT NOT NULL, " +
42 | COLUMN_KEEP_ALIVE + " INTEGER NOT NULL DEFAULT 0, " +
43 | COLUMN_REDUCE_PERM + " INTEGER NOT NULL DEFAULT 0, " +
44 | COLUMN_TARGET_PERM + " TEXT);"
45 |
46 | // environment 表
47 | const val TABLE_ENVIRONMENT: String = "environment"
48 | const val COLUMN_KEY: String = "env_key"
49 | const val COLUMN_VALUE: String = "env_value"
50 |
51 | private const val TABLE_ENVIRONMENT_CREATE = "CREATE TABLE " + TABLE_ENVIRONMENT + " (" +
52 | COLUMN_KEY + " TEXT NOT NULL, " +
53 | COLUMN_VALUE + " TEXT NOT NULL, " +
54 | "PRIMARY KEY (" + COLUMN_KEY + "));"
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/database/EnvironmentDao.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.database
2 |
3 | import android.content.ContentValues
4 | import android.database.sqlite.SQLiteDatabase
5 | import yangfentuozi.runner.service.data.EnvInfo
6 |
7 | class EnvironmentDao(private val db: SQLiteDatabase) {
8 | // 插入键值对
9 | fun insert(key: String?, value: String?): Boolean {
10 | val values = ContentValues()
11 | values.put(DataDbHelper.COLUMN_KEY, key)
12 | values.put(DataDbHelper.COLUMN_VALUE, value)
13 | val result = db.insert(DataDbHelper.TABLE_ENVIRONMENT, null, values)
14 | return result != -1L // 返回是否插入成功
15 | }
16 |
17 | // 更新键值对
18 | fun update(key: String?, value: String?): Boolean {
19 | val values = ContentValues()
20 | values.put(DataDbHelper.COLUMN_VALUE, value)
21 | val rowsAffected = db.update(
22 | DataDbHelper.TABLE_ENVIRONMENT,
23 | values,
24 | DataDbHelper.COLUMN_KEY + " = ?",
25 | arrayOf(key)
26 | )
27 | return rowsAffected > 0 // 返回是否更新成功
28 | }
29 |
30 | fun update(fromKey: String?, fromValue: String?, toKey: String?, toValue: String?): Boolean {
31 | val values = ContentValues()
32 | values.put(DataDbHelper.COLUMN_KEY, toKey)
33 | values.put(DataDbHelper.COLUMN_VALUE, toValue)
34 |
35 | val rowsAffected = db.update(
36 | DataDbHelper.TABLE_ENVIRONMENT,
37 | values,
38 | DataDbHelper.COLUMN_KEY + " = ? AND " + DataDbHelper.COLUMN_VALUE + " = ?",
39 | arrayOf(fromKey, fromValue)
40 | )
41 | return rowsAffected > 0 // 返回是否更新成功
42 | }
43 |
44 | // 根据键获取值
45 | fun getValue(key: String?): String? {
46 | val cursor = db.query(
47 | DataDbHelper.TABLE_ENVIRONMENT,
48 | arrayOf(DataDbHelper.COLUMN_VALUE),
49 | DataDbHelper.COLUMN_KEY + " = ?",
50 | arrayOf(key),
51 | null,
52 | null,
53 | null
54 | )
55 |
56 | var value: String? = null
57 | if (cursor.moveToFirst()) {
58 | value = cursor.getString(cursor.getColumnIndexOrThrow(DataDbHelper.COLUMN_VALUE))
59 | }
60 | cursor.close()
61 | return value
62 | }
63 |
64 | // 删除键值对
65 | fun delete(key: String?) {
66 | db.delete(
67 | DataDbHelper.TABLE_ENVIRONMENT,
68 | DataDbHelper.COLUMN_KEY + " = ?",
69 | arrayOf(key)
70 | )
71 | }
72 |
73 | val all: ArrayList?
74 | // 获取所有键值对
75 | get() {
76 | val cursor = db.query(
77 | DataDbHelper.TABLE_ENVIRONMENT,
78 | arrayOf(DataDbHelper.COLUMN_KEY, DataDbHelper.COLUMN_VALUE),
79 | null,
80 | null,
81 | null,
82 | null,
83 | null
84 | )
85 |
86 | val arrayList = ArrayList()
87 | while (cursor.moveToNext()) {
88 | val key = cursor.getString(cursor.getColumnIndexOrThrow(DataDbHelper.COLUMN_KEY))
89 | val value =
90 | cursor.getString(cursor.getColumnIndexOrThrow(DataDbHelper.COLUMN_VALUE))
91 | val envInfo = EnvInfo()
92 | envInfo.key = key
93 | envInfo.value = value
94 | arrayList.add(envInfo)
95 | }
96 | cursor.close()
97 | return arrayList
98 | }
99 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/fakecontext/FakeContext.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.fakecontext
2 |
3 | import android.content.AttributionSource
4 | import android.content.Context
5 | import android.content.ContextWrapper
6 | import android.os.Build
7 | import android.system.Os
8 | import androidx.annotation.RequiresApi
9 |
10 | class FakeContext private constructor() : ContextWrapper(Workarounds.systemContext) {
11 | override fun getPackageName(): String {
12 | return PACKAGE_NAME
13 | }
14 |
15 | override fun getOpPackageName(): String {
16 | return PACKAGE_NAME
17 | }
18 |
19 | @RequiresApi(Build.VERSION_CODES.S)
20 | override fun getAttributionSource(): AttributionSource {
21 | val builder = AttributionSource.Builder(Os.getuid())
22 | builder.setPackageName(PACKAGE_NAME)
23 | return builder.build()
24 | }
25 |
26 | // @Override to be added on SDK upgrade for Android 14
27 | @Suppress("unused")
28 | override fun getDeviceId(): Int {
29 | return 0
30 | }
31 |
32 | override fun getApplicationContext(): Context {
33 | return this
34 | }
35 |
36 | companion object {
37 | var PACKAGE_NAME: String = if (Os.getuid() == 0) "root" else "com.android.shell"
38 | private val INSTANCE = FakeContext()
39 |
40 | fun get(): FakeContext {
41 | return INSTANCE
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/fakecontext/Workarounds.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.fakecontext
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.util.Log
6 | import yangfentuozi.runner.service.ServiceImpl
7 | import java.lang.reflect.Constructor
8 |
9 | @SuppressLint("PrivateApi,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi")
10 | object Workarounds {
11 | private var ACTIVITY_THREAD_CLASS: Class<*>? = null
12 | private var ACTIVITY_THREAD: Any? = null
13 |
14 | init {
15 | try {
16 | ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread")
17 | val activityThreadConstructor: Constructor<*>? =
18 | ACTIVITY_THREAD_CLASS?.getDeclaredConstructor()
19 | activityThreadConstructor?.isAccessible = true
20 | ACTIVITY_THREAD = activityThreadConstructor?.newInstance()
21 |
22 | val sCurrentActivityThreadField =
23 | ACTIVITY_THREAD_CLASS?.getDeclaredField("sCurrentActivityThread")
24 | sCurrentActivityThreadField?.isAccessible = true
25 | sCurrentActivityThreadField?.set(null, ACTIVITY_THREAD)
26 | } catch (e: Exception) {
27 | throw AssertionError(e)
28 | }
29 | }
30 |
31 | val systemContext: Context?
32 | get() {
33 | try {
34 | val getSystemContextMethod =
35 | ACTIVITY_THREAD_CLASS!!.getDeclaredMethod("getSystemContext")
36 | return getSystemContextMethod.invoke(ACTIVITY_THREAD) as Context?
37 | } catch (throwable: Throwable) {
38 | Log.e(
39 | ServiceImpl.TAG,
40 | "Workarounds: Failed to get system context: ${throwable.stackTraceToString()}",
41 | throwable
42 | )
43 | return null
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/util/JniUtilsBase.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.util.Log
5 |
6 | @SuppressLint("UnsafeDynamicallyLoadedCode")
7 | abstract class JniUtilsBase {
8 | var isLibraryLoaded: Boolean = false
9 | private set
10 |
11 | @Synchronized
12 | fun loadLibrary() {
13 | if (!this.isLibraryLoaded) {
14 | try {
15 | System.load(this.jniPath)
16 | this.isLibraryLoaded = true
17 | } catch (e: UnsatisfiedLinkError) {
18 | Log.e("ProcessUtils", "Failed to load native library: ", e)
19 | this.isLibraryLoaded = false
20 | }
21 | }
22 | }
23 |
24 | abstract val jniPath: String
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/service/util/ProcessUtils.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.service.util
2 |
3 | import yangfentuozi.runner.service.ServiceImpl
4 | import yangfentuozi.runner.service.data.ProcessInfo
5 |
6 | class ProcessUtils : JniUtilsBase() {
7 | external fun sendSignal(pid: Int, signal: Int): Boolean
8 |
9 | external fun getProcesses(): Array?
10 |
11 | override val jniPath: String
12 | get() = ServiceImpl.JNI_PROCESS_UTILS
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/activity/CrashReportActivity.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.activity
2 |
3 | import android.graphics.Typeface
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.os.SystemProperties
7 | import android.text.SpannableString
8 | import android.text.Spanned
9 | import android.text.style.StyleSpan
10 | import android.view.MenuItem
11 | import yangfentuozi.runner.base.BaseActivity
12 | import yangfentuozi.runner.databinding.ActivityCrashReportBinding
13 | import yangfentuozi.runner.util.ThrowableUtil.toErrorDialog
14 | import java.io.FileOutputStream
15 | import java.io.IOException
16 |
17 | class CrashReportActivity : BaseActivity() {
18 |
19 | private lateinit var binding: ActivityCrashReportBinding
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | binding = ActivityCrashReportBinding.inflate(layoutInflater)
24 | setContentView(binding.root)
25 |
26 | binding.appBar.setLiftable(true)
27 | setSupportActionBar(binding.toolbar)
28 | supportActionBar?.apply {
29 | setDisplayHomeAsUpEnabled(true)
30 | }
31 |
32 | val crashFile = intent.getStringExtra("crash_file")
33 | val crashInfo = intent.getStringExtra("crash_info")
34 |
35 | binding.crashFile.text = crashFile
36 |
37 | val crashInfoTextView = binding.crashInfo.apply {
38 | append(getBoldText("VERSION.RELEASE: "))
39 | append(Build.VERSION.RELEASE)
40 | append("\n")
41 |
42 | append(getBoldText("VERSION.SDK_INT: "))
43 | append(Build.VERSION.SDK_INT.toString())
44 | append("\n")
45 |
46 | append(getBoldText("BUILD_TYPE: "))
47 | append(Build.TYPE)
48 | append("\n")
49 |
50 | append(getBoldText("CPU_ABI: "))
51 | append(SystemProperties.get("ro.product.cpu.abi"))
52 | append("\n")
53 |
54 | append(getBoldText("CPU_SUPPORTED_ABIS: "))
55 | append(Build.SUPPORTED_ABIS.contentToString())
56 | append("\n\n$crashInfo")
57 | }
58 |
59 | try {
60 | FileOutputStream(crashFile).use { out ->
61 | out.write(crashInfoTextView.text.toString().toByteArray())
62 | }
63 | } catch (e: IOException) {
64 | e.toErrorDialog(this)
65 | }
66 | }
67 |
68 | private fun getBoldText(text: String): CharSequence {
69 | return SpannableString(text).apply {
70 | setSpan(StyleSpan(Typeface.BOLD), 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
71 | }
72 | }
73 |
74 | override fun onDestroy() {
75 | super.onDestroy()
76 | mApp.finishApp()
77 | }
78 |
79 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
80 | return if (item.itemId == android.R.id.home) {
81 | finish()
82 | true
83 | } else {
84 | super.onOptionsItemSelected(item)
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/activity/InstallTermExtActivity.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.activity
2 |
3 | import android.content.Intent
4 | import android.graphics.Typeface
5 | import android.net.Uri
6 | import android.os.Bundle
7 | import android.view.MenuItem
8 | import android.widget.ScrollView
9 | import android.widget.Toast
10 | import androidx.annotation.StringRes
11 | import yangfentuozi.runner.R
12 | import yangfentuozi.runner.Runner
13 | import yangfentuozi.runner.base.BaseActivity
14 | import yangfentuozi.runner.databinding.ActivityStreamActivityBinding
15 | import yangfentuozi.runner.service.ServiceImpl
16 | import yangfentuozi.runner.service.callback.IInstallTermExtCallback
17 | import java.io.File
18 | import java.io.FileNotFoundException
19 | import java.io.FileOutputStream
20 | import java.io.IOException
21 | import java.io.InputStream
22 |
23 |
24 | class InstallTermExtActivity : BaseActivity() {
25 | private lateinit var binding: ActivityStreamActivityBinding
26 | private var callback: IInstallTermExtCallback? = null
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | binding = ActivityStreamActivityBinding.inflate(layoutInflater)
31 | setContentView(binding.root)
32 | binding.appBar.setLiftable(true)
33 | setSupportActionBar(binding.toolbar)
34 | supportActionBar?.apply {
35 | setDisplayHomeAsUpEnabled(true)
36 | }
37 | binding.text1.typeface = Typeface.MONOSPACE
38 |
39 | val action = intent.action
40 | val type = intent.type
41 |
42 | if (Intent.ACTION_VIEW == action && type != null) {
43 | val uri = intent.data
44 | if (uri != null)
45 | handleReceivedFile(uri)
46 | else {
47 | onMessage("! Invalid file")
48 | return
49 | }
50 | } else if (Intent.ACTION_SEND == action && type != null) {
51 | val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM)
52 | if (uri != null)
53 | handleReceivedFile(uri)
54 | else {
55 | onMessage("! Invalid file")
56 | return
57 | }
58 | } else {
59 | onMessage("! Invalid intent")
60 | return
61 | }
62 | }
63 |
64 | private fun handleReceivedFile(uri: Uri) {
65 | var input: InputStream? = null
66 | try {
67 | input = contentResolver.openInputStream(uri)
68 | } catch (e: FileNotFoundException) {
69 | onMessage("! File not found:\n" + e.stackTraceToString())
70 | return
71 | }
72 | if (input == null) {
73 | onMessage("! Failed to open file")
74 | return
75 | }
76 | val termExtCacheDir = File(externalCacheDir, "termExtCache")
77 | ServiceImpl.rmRF(termExtCacheDir)
78 | termExtCacheDir.mkdirs()
79 | val file = File(termExtCacheDir, "termux_ext.zip")
80 | try {
81 | if (!file.exists()) {
82 | file.createNewFile()
83 | }
84 | val output = FileOutputStream(file)
85 | input.copyTo(output, bufferSize = ServiceImpl.PAGE_SIZE)
86 | input.close()
87 | output.close()
88 | } catch (_: IOException) {
89 | onMessage("! Failed to copy file")
90 | return
91 | }
92 |
93 | if (!Runner.pingServer()) {
94 | showErrAndFinish(R.string.service_not_running)
95 | finish()
96 | }
97 |
98 | callback = object : IInstallTermExtCallback.Stub() {
99 | override fun onMessage(message: String?) {
100 | this@InstallTermExtActivity.onMessage(message)
101 | }
102 |
103 | override fun onExit(isSuccessful: Boolean) {
104 | onMessage(if (isSuccessful) "- Installation successful" else "! Installation failed")
105 | onMessage("\n- Cleanup temp: ${termExtCacheDir.absolutePath}")
106 | ServiceImpl.rmRF(termExtCacheDir)
107 | callback = null
108 | }
109 | }
110 |
111 | Runner.service?.installTermExt(file.absolutePath, callback)
112 | }
113 |
114 | private fun onMessage(message: String?) {
115 | runOnMainThread {
116 | binding.text1.append(message + "\n")
117 | binding.scrollView.post {
118 | binding.scrollView.fullScroll(ScrollView.FOCUS_DOWN)
119 | }
120 | }
121 | }
122 |
123 | private fun showErrAndFinish(@StringRes resId: Int) {
124 | runOnMainThread {
125 | Toast.makeText(this, resId, Toast.LENGTH_SHORT).show()
126 | finish()
127 | }
128 | }
129 |
130 | override fun onDestroy() {
131 | super.onDestroy()
132 | callback = null
133 | }
134 |
135 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
136 | return if (item.itemId == android.R.id.home) {
137 | finish()
138 | true
139 | } else {
140 | super.onOptionsItemSelected(item)
141 | }
142 | }
143 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/activity/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.activity
2 |
3 | import android.os.Bundle
4 | import android.text.method.LinkMovementMethod
5 | import androidx.core.text.HtmlCompat
6 | import androidx.navigation.fragment.NavHostFragment
7 | import androidx.navigation.ui.AppBarConfiguration
8 | import androidx.navigation.ui.NavigationUI
9 | import com.google.android.material.appbar.AppBarLayout
10 | import com.google.android.material.appbar.MaterialToolbar
11 | import yangfentuozi.runner.BuildConfig
12 | import yangfentuozi.runner.R
13 | import yangfentuozi.runner.Runner
14 | import yangfentuozi.runner.base.BaseActivity
15 | import yangfentuozi.runner.base.BaseDialogBuilder
16 | import yangfentuozi.runner.databinding.ActivityMainBinding
17 | import yangfentuozi.runner.databinding.DialogAboutBinding
18 | import yangfentuozi.runner.ui.dialog.BlurBehindDialogBuilder
19 | import yangfentuozi.runner.util.ThrowableUtil.toErrorDialog
20 | import java.util.Locale
21 |
22 | class MainActivity : BaseActivity() {
23 |
24 | lateinit var appBar: AppBarLayout
25 | lateinit var toolbar: MaterialToolbar
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 |
30 | val binding = ActivityMainBinding.inflate(layoutInflater)
31 | setContentView(binding.root)
32 |
33 | val appBarConfiguration = AppBarConfiguration.Builder(
34 | R.id.navigation_home,
35 | R.id.navigation_runner,
36 | R.id.navigation_terminal,
37 | R.id.navigation_proc,
38 | R.id.navigation_settings
39 | ).build()
40 |
41 | val fragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main)
42 | val navController = (fragment as NavHostFragment).navController
43 | NavigationUI.setupWithNavController(binding.toolbar, navController, appBarConfiguration)
44 | NavigationUI.setupWithNavController(binding.navView, navController)
45 | binding.navView.menu.findItem(R.id.navigation_terminal).isEnabled = false
46 | binding.navView.menu.findItem(R.id.navigation_terminal).isVisible = false
47 |
48 | binding.appBar.setLiftable(true)
49 | binding.toolbar.inflateMenu(R.menu.menu_home)
50 |
51 | binding.toolbar.setOnMenuItemClickListener { item ->
52 | when (item.itemId) {
53 | R.id.menu_stop_server -> {
54 | if (!Runner.pingServer()) return@setOnMenuItemClickListener true
55 | try {
56 | BaseDialogBuilder(this)
57 | .setTitle(R.string.warning)
58 | .setMessage(R.string.confirm_stop_server)
59 | .setPositiveButton(android.R.string.ok) { _, _ ->
60 | Thread {
61 | try {
62 | Runner.tryUnbindService(true)
63 | } catch (e: Exception) {
64 | e.toErrorDialog(this)
65 | }
66 | }.start()
67 | }
68 | .setNegativeButton(android.R.string.cancel, null)
69 | .show()
70 | } catch (_: BaseDialogBuilder.DialogShowingException) {
71 | }
72 | true
73 | }
74 |
75 | R.id.menu_about -> {
76 | showAbout()
77 | true
78 | }
79 |
80 | else -> true
81 | }
82 | }
83 |
84 | toolbar = binding.toolbar
85 | appBar = binding.appBar
86 | }
87 |
88 | private fun showAbout() {
89 | val binding = DialogAboutBinding.inflate(layoutInflater, null, false)
90 | binding.designAboutTitle.setText(R.string.app_name)
91 | binding.designAboutInfo.movementMethod = LinkMovementMethod.getInstance()
92 | binding.designAboutInfo.text = HtmlCompat.fromHtml(
93 | getString(
94 | R.string.about_view_source_code,
95 | "GitHub"
96 | ), HtmlCompat.FROM_HTML_MODE_LEGACY
97 | )
98 | binding.designAboutVersion.text = String.format(
99 | Locale.getDefault(),
100 | "%s (%d)",
101 | BuildConfig.VERSION_NAME,
102 | BuildConfig.VERSION_CODE
103 | )
104 |
105 | try {
106 | BlurBehindDialogBuilder(this)
107 | .setView(binding.root)
108 | .show()
109 | } catch (_: BaseDialogBuilder.DialogShowingException) {
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/activity/PackActivity.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.activity
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import android.view.MenuItem
6 | import android.widget.Toast
7 | import androidx.appcompat.app.ActionBar
8 | import yangfentuozi.runner.R
9 | import yangfentuozi.runner.base.BaseActivity
10 | import yangfentuozi.runner.databinding.ActivityPackBinding
11 |
12 | class PackActivity : BaseActivity() {
13 | private var binding: ActivityPackBinding? = null
14 |
15 | @SuppressLint("SetTextI18n")
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | binding = ActivityPackBinding.inflate(layoutInflater)
19 | setContentView(binding!!.getRoot())
20 |
21 | binding!!.appBar.setLiftable(true)
22 | setSupportActionBar(binding!!.toolbar)
23 | val actionBar: ActionBar?
24 | if ((supportActionBar.also {
25 | actionBar = it
26 | }) != null) actionBar!!.setDisplayHomeAsUpEnabled(true)
27 |
28 | val id = intent.getIntExtra("id", -1)
29 | if (id == -1) {
30 | Toast.makeText(this, R.string.finish, Toast.LENGTH_LONG).show()
31 | finish()
32 | }
33 |
34 | binding!!.packName.setText(intent.getStringExtra("name"))
35 | binding!!.packPackageName.setText("runner.app." + intent.getStringExtra("name"))
36 | binding!!.packVersionName.setText("1.0")
37 | binding!!.packVersionCode.setText("1")
38 | }
39 |
40 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
41 | return if (item.itemId == android.R.id.home) {
42 | finish()
43 | true
44 | } else {
45 | super.onOptionsItemSelected(item)
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/activity/envmanage/EnvManageActivity.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.activity.envmanage
2 |
3 | import android.os.Bundle
4 | import android.util.TypedValue
5 | import android.view.LayoutInflater
6 | import android.view.MenuItem
7 | import android.view.View
8 | import android.widget.Toast
9 | import androidx.appcompat.app.ActionBar
10 | import rikka.recyclerview.addEdgeSpacing
11 | import rikka.recyclerview.addItemSpacing
12 | import rikka.recyclerview.fixEdgeEffect
13 | import yangfentuozi.runner.R
14 | import yangfentuozi.runner.Runner
15 | import yangfentuozi.runner.base.BaseActivity
16 | import yangfentuozi.runner.base.BaseDialogBuilder
17 | import yangfentuozi.runner.databinding.ActivityEnvManageBinding
18 | import yangfentuozi.runner.databinding.DialogEditEnvBinding
19 | import yangfentuozi.runner.service.data.EnvInfo
20 |
21 | class EnvManageActivity : BaseActivity() {
22 | private lateinit var mBinding: ActivityEnvManageBinding
23 | private val mAdapter: EnvAdapter = EnvAdapter(this)
24 | val binding get() = mBinding
25 |
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 | mBinding = ActivityEnvManageBinding.inflate(layoutInflater)
29 | setContentView(mBinding.getRoot())
30 |
31 | mBinding.recyclerView.apply {
32 | adapter = mAdapter
33 | fixEdgeEffect(true, true)
34 | addItemSpacing(top = 4f, bottom = 4f, unit = TypedValue.COMPLEX_UNIT_DIP)
35 | addEdgeSpacing(
36 | top = 4f,
37 | bottom = 4f,
38 | left = 16f,
39 | right = 16f,
40 | unit = TypedValue.COMPLEX_UNIT_DIP
41 | )
42 | }
43 | mBinding.appBar.setLiftable(true)
44 | setSupportActionBar(mBinding.toolbar)
45 | val actionBar: ActionBar?
46 | if ((supportActionBar.also {
47 | actionBar = it
48 | }) != null) actionBar!!.setDisplayHomeAsUpEnabled(true)
49 |
50 | mBinding.add.setOnClickListener {
51 | showAddDialog()
52 | }
53 |
54 | mBinding.toolbar.setOnClickListener { v: View? ->
55 | mBinding.recyclerView.smoothScrollToPosition(
56 | 0
57 | )
58 | }
59 | }
60 |
61 | fun showAddDialog() {
62 | val dialogBinding = DialogEditEnvBinding.inflate(LayoutInflater.from(this))
63 |
64 | try {
65 | BaseDialogBuilder(this)
66 | .setTitle(R.string.edit)
67 | .setView(dialogBinding.root)
68 | .setPositiveButton(android.R.string.ok) { _, _ ->
69 | if (!Runner.pingServer()) {
70 | Toast.makeText(
71 | this,
72 | R.string.service_not_running,
73 | Toast.LENGTH_SHORT
74 | ).show()
75 | return@setPositiveButton
76 | }
77 |
78 | mAdapter.add(EnvInfo().apply {
79 | key = dialogBinding.key.text.toString()
80 | value = dialogBinding.value.text.toString()
81 | })
82 | }
83 | .show()
84 | } catch (_: BaseDialogBuilder.DialogShowingException) {
85 | }
86 | }
87 |
88 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
89 | return if (item.itemId == android.R.id.home) {
90 | finish()
91 | true
92 | } else {
93 | super.onOptionsItemSelected(item)
94 | }
95 | }
96 |
97 | override fun onContextItemSelected(item: MenuItem): Boolean {
98 | return mAdapter.onContextItemSelected(item)
99 | }
100 |
101 | override fun onStart() {
102 | super.onStart()
103 | Runner.refreshStatus()
104 | mAdapter.updateData()
105 | }
106 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/activity/envmanage/ItemAdapter.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.activity.envmanage
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import androidx.core.view.size
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
8 | import yangfentuozi.runner.R
9 | import yangfentuozi.runner.databinding.DialogEditEnvE2Binding
10 | import yangfentuozi.runner.databinding.ItemEnvItemBinding
11 |
12 |
13 | class ItemAdapter(private val mContext: EnvManageActivity, value: String) :
14 | RecyclerView.Adapter() {
15 | var data: String = ""
16 | var dataList: ArrayList = ArrayList()
17 |
18 | init {
19 | data = value
20 | dataList = ArrayList(value.split(":"))
21 |
22 | }
23 |
24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
25 | return ViewHolder(ItemEnvItemBinding.inflate(mContext.layoutInflater, parent, false))
26 | }
27 |
28 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
29 | var position = holder.bindingAdapterPosition
30 | if (position == itemCount - 1) {
31 | holder.binding.value.visibility = ViewGroup.GONE
32 | holder.binding.remove.apply {
33 | contentDescription = mContext.getString(R.string.add)
34 | icon = mContext.getDrawable(R.drawable.ic_add_24)
35 | setOnClickListener {
36 | dataList.add("")
37 | notifyItemInserted(holder.bindingAdapterPosition)
38 | mContext.binding.recyclerView.scrollToPosition(dataList.size - 1)
39 | }
40 | }
41 | return
42 | }
43 | val info = dataList[position]
44 | holder.binding.value.apply {
45 | setText(info)
46 | keyListener = null
47 | setOnClickListener {
48 | val dialogBinding = DialogEditEnvE2Binding.inflate(mContext.layoutInflater)
49 | MaterialAlertDialogBuilder(mContext)
50 | .setTitle(R.string.edit)
51 | .setView(dialogBinding.root)
52 | .setNegativeButton(android.R.string.ok) { _, _ ->
53 | dataList[position] = dialogBinding.value.text.toString()
54 | text = dialogBinding.value.text
55 | }
56 | .show()
57 | }
58 | }
59 | holder.binding.remove.setOnClickListener {
60 | holder.binding.root.apply {
61 | setFocusable(false)
62 | setFocusableInTouchMode(false)
63 | clearFocus()
64 |
65 | for (i in 0..(root) {
13 | init {
14 | binding.button1.setOnClickListener { v ->
15 | Runner.requestPermission()
16 | }
17 | }
18 |
19 | companion object {
20 | val CREATOR: Creator = Creator { inflater: LayoutInflater?, parent: ViewGroup? ->
21 | val outer = HomeItemContainerBinding.inflate(
22 | inflater!!, parent, false
23 | )
24 | val inner = HomeShizukuPermRequestBinding.inflate(inflater, outer.getRoot(), true)
25 | GrantShizukuPermViewHolder(inner, outer.getRoot())
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/fragment/home/HomeAdapter.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.fragment.home
2 |
3 | import android.annotation.SuppressLint
4 | import rikka.recyclerview.IdBasedRecyclerViewAdapter
5 | import rikka.recyclerview.IndexCreatorPool
6 | import yangfentuozi.runner.Runner
7 |
8 | class HomeAdapter(private val fragment: HomeFragment) : IdBasedRecyclerViewAdapter(ArrayList()) {
9 |
10 | private val shizukuPermissionListener = Runner.ShizukuPermissionListener {
11 | var position = findPositionById(ID_GRANT_SHIZUKU_PERM)
12 | if (position == -1) {
13 | if (it) return@ShizukuPermissionListener
14 | position = 2
15 | addItemAt(position, GrantShizukuPermViewHolder.CREATOR, null, ID_GRANT_SHIZUKU_PERM)
16 | notifyItemInserted(position)
17 | } else {
18 | if (!it) return@ShizukuPermissionListener
19 | removeItemAt(position)
20 | notifyItemRemoved(position)
21 | }
22 | }
23 |
24 | private val shizukuStatusListener = Runner.ShizukuStatusListener {
25 | val position = 1
26 | removeItemAt(position)
27 | addItemAt(position, ShizukuStatusViewHolder.CREATOR, null, ID_SHIZUKU_STATUS)
28 | notifyItemChanged(position)
29 | }
30 |
31 | private val serviceStatusListener = Runner.ServiceStatusListener {
32 | var position = 0
33 | removeItemAt(position)
34 | addItemAt(position, ServiceStatusViewHolder.CREATOR, null, ID_SERVICE_STATUS)
35 | notifyItemChanged(position)
36 |
37 | position = findPositionById(ID_TERM_EXT_STATUS)
38 | if (position == -1) {
39 | if (!it) return@ServiceStatusListener
40 | position = if (findPositionById(ID_GRANT_SHIZUKU_PERM) == -1) 2 else 3
41 | addItemAt(position, TermExtStatusViewHolder.CREATOR, fragment, ID_TERM_EXT_STATUS)
42 | notifyItemInserted(position)
43 | } else {
44 | if (it) return@ServiceStatusListener
45 | removeItemAt(position)
46 | notifyItemRemoved(position)
47 | }
48 | }
49 |
50 | init {
51 | updateData()
52 | setHasStableIds(true)
53 | }
54 |
55 | companion object {
56 | private const val ID_SERVICE_STATUS = 0L
57 | private const val ID_SHIZUKU_STATUS = 1L
58 | private const val ID_GRANT_SHIZUKU_PERM = 2L
59 | private const val ID_TERM_EXT_STATUS = 3L
60 | }
61 |
62 | override fun onCreateCreatorPool(): IndexCreatorPool {
63 | return IndexCreatorPool()
64 | }
65 |
66 | fun findPositionById(id: Long): Int {
67 | for (i in 0 until ids.size)
68 | if (ids[i] == id)
69 | return i
70 | return -1
71 | }
72 |
73 | @SuppressLint("NotifyDataSetChanged")
74 | fun updateData() {
75 | clear()
76 | addItem(ServiceStatusViewHolder.CREATOR, null, ID_SERVICE_STATUS)
77 | addItem(ShizukuStatusViewHolder.CREATOR, null, ID_SHIZUKU_STATUS)
78 |
79 | if (!Runner.shizukuPermission) {
80 | addItem(GrantShizukuPermViewHolder.CREATOR, null, ID_GRANT_SHIZUKU_PERM)
81 | }
82 |
83 | if (Runner.pingServer()) {
84 | addItem(TermExtStatusViewHolder.CREATOR, fragment, ID_TERM_EXT_STATUS)
85 | }
86 |
87 | notifyDataSetChanged()
88 | }
89 |
90 | fun registerListeners() {
91 | Runner.addShizukuPermissionListener(shizukuPermissionListener)
92 | Runner.addShizukuStatusListener(shizukuStatusListener)
93 | Runner.addServiceStatusListener(serviceStatusListener)
94 | }
95 |
96 | fun unregisterListeners() {
97 | Runner.removeShizukuPermissionListener(shizukuPermissionListener)
98 | Runner.removeShizukuStatusListener(shizukuStatusListener)
99 | Runner.removeServiceStatusListener(serviceStatusListener)
100 | }
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/fragment/home/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.fragment.home
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.util.TypedValue
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.activity.result.contract.ActivityResultContracts
11 | import rikka.recyclerview.addEdgeSpacing
12 | import rikka.recyclerview.addItemSpacing
13 | import rikka.recyclerview.fixEdgeEffect
14 | import rikka.widget.borderview.BorderRecyclerView
15 | import yangfentuozi.runner.R
16 | import yangfentuozi.runner.Runner
17 | import yangfentuozi.runner.base.BaseFragment
18 | import yangfentuozi.runner.databinding.FragmentHomeBinding
19 | import yangfentuozi.runner.ui.activity.InstallTermExtActivity
20 |
21 | class HomeFragment : BaseFragment() {
22 | var binding: FragmentHomeBinding? = null
23 | private set
24 | private var recyclerView: BorderRecyclerView? = null
25 | private val mAdapter = HomeAdapter(this)
26 |
27 | override fun onCreateView(
28 | inflater: LayoutInflater,
29 | container: ViewGroup?, savedInstanceState: Bundle?
30 | ): View? {
31 | binding = FragmentHomeBinding.inflate(inflater, container, false)
32 |
33 | recyclerView = binding!!.list.apply {
34 | adapter = this@HomeFragment.mAdapter
35 | fixEdgeEffect(true, true)
36 | addItemSpacing(0f, 4f, 0f, 4f, TypedValue.COMPLEX_UNIT_DIP)
37 | addEdgeSpacing(16f, 4f, 16f, 4f, TypedValue.COMPLEX_UNIT_DIP)
38 | }
39 |
40 | return binding!!.getRoot()
41 | }
42 |
43 | override fun onDestroyView() {
44 | super.onDestroyView()
45 | binding = null
46 | }
47 |
48 | override fun onStart() {
49 | super.onStart()
50 | val l = View.OnClickListener { v: View? -> recyclerView!!.smoothScrollToPosition(0) }
51 | toolbar.setOnClickListener(l)
52 | Runner.refreshStatus()
53 | mAdapter.updateData()
54 | mAdapter.registerListeners()
55 | }
56 |
57 | override fun onStop() {
58 | super.onStop()
59 | mAdapter.unregisterListeners()
60 | }
61 |
62 | fun installTermExt() {
63 | val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
64 | addCategory(Intent.CATEGORY_OPENABLE)
65 | type = "application/zip"
66 | }
67 | pickFileLauncher.launch(Intent.createChooser(intent, getString(R.string.pick_term_ext)))
68 | }
69 |
70 | private var pickFileLauncher =
71 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
72 | if (it.resultCode == Activity.RESULT_OK) {
73 | it.data?.data?.let { uri ->
74 | val mimeType = mMainActivity.contentResolver.getType(uri)
75 | if (mimeType != "application/zip") {
76 | return@let
77 | }
78 |
79 | val installIntent = Intent(mMainActivity, InstallTermExtActivity::class.java).apply {
80 | action = Intent.ACTION_VIEW
81 | setDataAndType(uri, "application/zip")
82 | addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
83 | }
84 |
85 | startActivity(installIntent)
86 | }
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/fragment/home/ServiceStatusViewHolder.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.fragment.home
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.ImageView
7 | import android.widget.TextView
8 | import androidx.core.content.ContextCompat
9 | import rikka.recyclerview.BaseViewHolder
10 | import yangfentuozi.runner.R
11 | import yangfentuozi.runner.Runner
12 | import yangfentuozi.runner.databinding.HomeItemContainerBinding
13 | import yangfentuozi.runner.databinding.HomeServiceStatusBinding
14 |
15 | class ServiceStatusViewHolder(binding: HomeServiceStatusBinding, root: View) :
16 | BaseViewHolder(root) {
17 | private val textView: TextView = binding.text1
18 | private val summaryView: TextView = binding.text2
19 | private val iconView: ImageView = binding.icon
20 |
21 | init {
22 | root.setOnClickListener {
23 | if (Runner.pingServer()) return@setOnClickListener
24 | Thread {
25 | Runner.tryBindService()
26 | }
27 | }
28 | }
29 |
30 | override fun onBind() {
31 | val context = this@ServiceStatusViewHolder.itemView.context
32 | val ok = Runner.pingServer()
33 | val version = Runner.serviceVersion
34 |
35 | iconView.setImageDrawable(
36 | ContextCompat.getDrawable(
37 | context,
38 | if (ok) R.drawable.ic_check_circle_outline_24 else R.drawable.ic_error_outline_24
39 | )
40 | )
41 |
42 | val title: String?
43 | val summary: String?
44 |
45 | if (ok) {
46 | title = context.getString(R.string.service_is_running)
47 | summary = context.getString(R.string.service_version, version)
48 | } else {
49 | title = context.getString(R.string.service_not_running)
50 | summary = ""
51 | }
52 |
53 | textView.text = title
54 | summaryView.text = summary
55 |
56 | if (summaryView.getText().isEmpty()) {
57 | summaryView.visibility = View.GONE
58 | } else {
59 | summaryView.visibility = View.VISIBLE
60 | }
61 | }
62 |
63 | companion object {
64 | val CREATOR: Creator = Creator { inflater: LayoutInflater?, parent: ViewGroup? ->
65 | val outer = HomeItemContainerBinding.inflate(
66 | inflater!!, parent, false
67 | )
68 | val inner = HomeServiceStatusBinding.inflate(inflater, outer.getRoot(), true)
69 | ServiceStatusViewHolder(inner, outer.getRoot())
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/fragment/home/ShizukuStatusViewHolder.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.fragment.home
2 |
3 | import android.text.TextUtils
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.ImageView
8 | import android.widget.TextView
9 | import androidx.core.content.ContextCompat
10 | import rikka.recyclerview.BaseViewHolder
11 | import yangfentuozi.runner.R
12 | import yangfentuozi.runner.Runner
13 | import yangfentuozi.runner.databinding.HomeItemContainerBinding
14 | import yangfentuozi.runner.databinding.HomeShizukuStatusBinding
15 |
16 | class ShizukuStatusViewHolder(binding: HomeShizukuStatusBinding, root: View) :
17 | BaseViewHolder(root) {
18 | private val textView: TextView = binding.text1
19 | private val summaryView: TextView = binding.text2
20 | private val iconView: ImageView = binding.icon
21 |
22 | override fun onBind() {
23 | val context = this@ShizukuStatusViewHolder.itemView.context
24 | val ok = Runner.shizukuStatus
25 | val isRoot = Runner.shizukuUid == 0
26 | val apiVersion = Runner.shizukuApiVersion
27 | val patchVersion = Runner.shizukuPatchVersion
28 |
29 | iconView.setImageDrawable(
30 | ContextCompat.getDrawable(
31 | context,
32 | if (ok) R.drawable.ic_check_circle_outline_24 else R.drawable.ic_error_outline_24
33 | )
34 | )
35 |
36 |
37 | val user = if (isRoot) "root" else "adb"
38 | val title: String?
39 | val summary: String?
40 |
41 | if (ok) {
42 | title = context.getString(R.string.shizuku_is_running)
43 | summary = context.getString(
44 | R.string.shizuku_version,
45 | user,
46 | "$apiVersion.$patchVersion"
47 | )
48 | } else {
49 | title = context.getString(R.string.shizuku_not_running)
50 | summary = ""
51 | }
52 |
53 | textView.text = title
54 | summaryView.text = summary
55 |
56 | if (TextUtils.isEmpty(summaryView.getText())) {
57 | summaryView.visibility = View.GONE
58 | } else {
59 | summaryView.visibility = View.VISIBLE
60 | }
61 | }
62 |
63 | companion object {
64 | val CREATOR: Creator = Creator { inflater: LayoutInflater?, parent: ViewGroup? ->
65 | val outer = HomeItemContainerBinding.inflate(
66 | inflater!!, parent, false
67 | )
68 | val inner = HomeShizukuStatusBinding.inflate(inflater, outer.getRoot(), true)
69 | ShizukuStatusViewHolder(inner, outer.getRoot())
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/fragment/home/TermExtStatusViewHolder.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.fragment.home
2 |
3 | import android.os.RemoteException
4 | import android.util.Log
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.Button
9 | import android.widget.TextView
10 | import rikka.recyclerview.BaseViewHolder
11 | import yangfentuozi.runner.R
12 | import yangfentuozi.runner.Runner
13 | import yangfentuozi.runner.base.BaseActivity
14 | import yangfentuozi.runner.base.BaseDialogBuilder
15 | import yangfentuozi.runner.databinding.HomeItemContainerBinding
16 | import yangfentuozi.runner.databinding.HomeTermExtStatusBinding
17 |
18 | class TermExtStatusViewHolder(binding: HomeTermExtStatusBinding, root: View) :
19 | BaseViewHolder(root) {
20 | private val title: TextView = binding.text1
21 | private val summaryView: TextView = binding.text2
22 | private val install: Button = binding.button1
23 | private val remove: Button = binding.button2
24 |
25 | override fun onBind() {
26 | install.setOnClickListener {
27 | data.installTermExt()
28 | }
29 |
30 | remove.setOnClickListener {
31 | if (!Runner.pingServer()) return@setOnClickListener
32 | try {
33 | BaseDialogBuilder(context as BaseActivity)
34 | .setTitle(R.string.will_remove_term_ext)
35 |
36 | } catch (_: BaseDialogBuilder.DialogShowingException){}
37 | Thread {
38 | try {
39 | Runner.service?.removeTermExt()
40 | data.runOnMainThread { onBind() }
41 | } catch (e: RemoteException) {
42 | Log.e("TermExtStatusViewHolder", "uninstall term ext error", e)
43 | }
44 | }.start()
45 | }
46 |
47 | if (!Runner.pingServer()) return
48 |
49 | try {
50 | val version = Runner.service?.getTermExtVersion()
51 | if (version == null || version.versionCode == -1) {
52 | // not install
53 | title.setText(R.string.term_ext_title)
54 | summaryView.visibility = View.GONE
55 | install.setText(R.string.install)
56 | remove.visibility = View.GONE
57 | } else {
58 | // installed
59 | title.setText(R.string.term_ext_title_installed)
60 | summaryView.visibility = View.VISIBLE
61 | summaryView.text = context.getString(
62 | R.string.term_ext_version,
63 | version.versionName,
64 | version.versionCode,
65 | version.abi
66 | )
67 | install.setText(R.string.reinstall)
68 | remove.visibility = View.VISIBLE
69 | }
70 | } catch (e: RemoteException) {
71 | Log.e("TermExtStatusViewHolder", "get term ext version error", e)
72 | }
73 | }
74 |
75 | companion object {
76 | val CREATOR: Creator = Creator { inflater: LayoutInflater?, parent: ViewGroup? ->
77 | val outer = HomeItemContainerBinding.inflate(
78 | inflater!!, parent, false
79 | )
80 | val inner = HomeTermExtStatusBinding.inflate(inflater, outer.getRoot(), true)
81 | TermExtStatusViewHolder(inner, outer.getRoot())
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/fragment/proc/ProcFragment.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.fragment.proc
2 |
3 | import android.content.DialogInterface
4 | import android.os.Bundle
5 | import android.os.RemoteException
6 | import android.util.TypedValue
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import android.widget.Toast
11 | import androidx.recyclerview.widget.LinearLayoutManager
12 | import androidx.recyclerview.widget.RecyclerView
13 | import rikka.recyclerview.addEdgeSpacing
14 | import rikka.recyclerview.addItemSpacing
15 | import rikka.recyclerview.fixEdgeEffect
16 | import yangfentuozi.runner.R
17 | import yangfentuozi.runner.Runner
18 | import yangfentuozi.runner.base.BaseDialogBuilder
19 | import yangfentuozi.runner.base.BaseFragment
20 | import yangfentuozi.runner.databinding.FragmentProcBinding
21 | import yangfentuozi.runner.util.ThrowableUtil.toErrorDialog
22 |
23 | class ProcFragment : BaseFragment() {
24 | private lateinit var mBinding: FragmentProcBinding
25 | val binding get() = mBinding
26 | private var recyclerView: RecyclerView? = null
27 | private lateinit var adapter: ProcAdapter
28 |
29 | override fun onCreateView(
30 | inflater: LayoutInflater,
31 | container: ViewGroup?, savedInstanceState: Bundle?
32 | ): View {
33 | mBinding = FragmentProcBinding.inflate(inflater, container, false)
34 | recyclerView = mBinding.recyclerView
35 | recyclerView!!.setLayoutManager(LinearLayoutManager(mMainActivity))
36 | recyclerView!!.fixEdgeEffect(false, true)
37 |
38 | adapter = ProcAdapter(mMainActivity, this)
39 |
40 | mBinding.recyclerView.apply {
41 | layoutManager = LinearLayoutManager(mMainActivity)
42 | fixEdgeEffect(true, true)
43 | addItemSpacing(0f, 4f, 0f, 4f, TypedValue.COMPLEX_UNIT_DIP)
44 | addEdgeSpacing(16f, 4f, 16f, 4f, TypedValue.COMPLEX_UNIT_DIP)
45 | adapter = this@ProcFragment.adapter
46 | }
47 | mBinding.swipeRefreshLayout.setOnRefreshListener {
48 | adapter.updateData()
49 | }
50 |
51 | mBinding.procKillAll.setOnClickListener { v ->
52 | if (Runner.pingServer()) {
53 | try {
54 | if (mBinding.recyclerView.adapter
55 | ?.itemCount == 0
56 | ) {
57 | return@setOnClickListener
58 | }
59 | } catch (_: NullPointerException) {
60 | }
61 | } else {
62 | Toast.makeText(
63 | mMainActivity,
64 | R.string.service_not_running,
65 | Toast.LENGTH_SHORT
66 | ).show()
67 | return@setOnClickListener
68 | }
69 | try {
70 | BaseDialogBuilder(mMainActivity)
71 | .setTitle(R.string.kill_all_processes)
72 | .setPositiveButton(android.R.string.ok) { dialog: DialogInterface?, which: Int ->
73 | Thread {
74 | if (Runner.pingServer()) {
75 | try {
76 | adapter.killAll()
77 | } catch (e: RemoteException) {
78 | e.toErrorDialog(mMainActivity)
79 | }
80 | } else {
81 | runOnMainThread {
82 | Toast.makeText(
83 | mMainActivity,
84 | R.string.service_not_running,
85 | Toast.LENGTH_SHORT
86 | ).show()
87 | }
88 | }
89 | }.start()
90 | }
91 | .setNegativeButton(android.R.string.cancel, null)
92 | .show()
93 | } catch (_: BaseDialogBuilder.DialogShowingException) {
94 | }
95 | }
96 | return mBinding.getRoot()
97 | }
98 |
99 | override fun onStart() {
100 | super.onStart()
101 | val l = View.OnClickListener { v: View? -> recyclerView!!.smoothScrollToPosition(0) }
102 | toolbar.setOnClickListener(l)
103 | adapter.updateData()
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/fragment/runner/RunnerFragment.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.fragment.runner
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.util.TypedValue
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.view.inputmethod.InputMethodManager
10 | import android.widget.Toast
11 | import androidx.recyclerview.widget.LinearLayoutManager
12 | import rikka.recyclerview.addEdgeSpacing
13 | import rikka.recyclerview.addItemSpacing
14 | import rikka.recyclerview.fixEdgeEffect
15 | import yangfentuozi.runner.R
16 | import yangfentuozi.runner.Runner
17 | import yangfentuozi.runner.base.BaseDialogBuilder
18 | import yangfentuozi.runner.base.BaseFragment
19 | import yangfentuozi.runner.databinding.DialogEditBinding
20 | import yangfentuozi.runner.databinding.FragmentRunnerBinding
21 | import yangfentuozi.runner.service.data.CommandInfo
22 |
23 | class RunnerFragment : BaseFragment() {
24 | private lateinit var mBinding: FragmentRunnerBinding
25 | private lateinit var adapter: CommandAdapter
26 | val binding get() = mBinding
27 |
28 | override fun onCreateView(
29 | inflater: LayoutInflater,
30 | container: ViewGroup?,
31 | savedInstanceState: Bundle?
32 | ): View {
33 | mBinding = FragmentRunnerBinding.inflate(inflater, container, false)
34 | return mBinding.root
35 | }
36 |
37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
38 | super.onViewCreated(view, savedInstanceState)
39 |
40 | adapter = CommandAdapter(mMainActivity, this)
41 |
42 | mBinding.recyclerView.apply {
43 | layoutManager = LinearLayoutManager(mMainActivity)
44 | fixEdgeEffect(true, true)
45 | addItemSpacing(0f, 4f, 0f, 4f, TypedValue.COMPLEX_UNIT_DIP)
46 | addEdgeSpacing(16f, 4f, 16f, 4f, TypedValue.COMPLEX_UNIT_DIP)
47 | adapter = this@RunnerFragment.adapter
48 | }
49 |
50 | mBinding.swipeRefreshLayout.setOnRefreshListener { adapter.updateData() }
51 |
52 | mBinding.add.setOnClickListener {
53 | if (mMainActivity.isDialogShowing) return@setOnClickListener
54 | showAddCommandDialog(-1)
55 | }
56 | adapter.updateData()
57 | }
58 |
59 | fun showAddCommandDialog(toPosition: Int) {
60 | val dialogBinding = DialogEditBinding.inflate(LayoutInflater.from(mMainActivity))
61 |
62 | dialogBinding.apply {
63 | targetPermParent.visibility = View.GONE
64 | reducePerm.setOnCheckedChangeListener { _, isChecked ->
65 | targetPermParent.visibility = if (isChecked) View.VISIBLE else View.GONE
66 | }
67 |
68 | name.requestFocus()
69 | name.postDelayed({
70 | (mMainActivity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
71 | .showSoftInput(name, InputMethodManager.SHOW_IMPLICIT)
72 | }, 200)
73 | }
74 |
75 | try {
76 | BaseDialogBuilder(mMainActivity)
77 | .setTitle(R.string.edit)
78 | .setView(dialogBinding.root)
79 | .setPositiveButton(android.R.string.ok) { _, _ ->
80 | if (!Runner.pingServer()) {
81 | Toast.makeText(
82 | mMainActivity,
83 | R.string.service_not_running,
84 | Toast.LENGTH_SHORT
85 | ).show()
86 | return@setPositiveButton
87 | }
88 |
89 | val newCommand = CommandInfo().apply {
90 | command = dialogBinding.command.text.toString()
91 | name = dialogBinding.name.text.toString()
92 | keepAlive = dialogBinding.keepAlive.isChecked
93 | reducePerm = dialogBinding.reducePerm.isChecked
94 | targetPerm =
95 | if (dialogBinding.reducePerm.isChecked) dialogBinding.targetPerm.text.toString() else null
96 | }
97 |
98 | if (toPosition == -1)
99 | adapter.add(newCommand)
100 | else
101 | adapter.addUnderOne(toPosition + 1, newCommand)
102 | }
103 | .show()
104 | } catch (_: BaseDialogBuilder.DialogShowingException) {
105 | }
106 | }
107 |
108 | override fun onContextItemSelected(item: android.view.MenuItem): Boolean {
109 | return adapter.onContextItemSelected(item)
110 | }
111 |
112 | override fun onStart() {
113 | super.onStart()
114 | toolbar.setOnClickListener {
115 | mBinding.recyclerView.smoothScrollToPosition(0)
116 | }
117 | }
118 |
119 | override fun onDestroyView() {
120 | super.onDestroyView()
121 | adapter.close()
122 | }
123 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/ui/fragment/terminal/TerminalFragment.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.ui.fragment.terminal
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import yangfentuozi.runner.base.BaseFragment
8 | import yangfentuozi.runner.databinding.FragmentTerminalBinding
9 |
10 | class TerminalFragment : BaseFragment() {
11 | private var binding: FragmentTerminalBinding? = null
12 |
13 | override fun onCreateView(
14 | inflater: LayoutInflater,
15 | container: ViewGroup?, savedInstanceState: Bundle?
16 | ): View? {
17 | binding = FragmentTerminalBinding.inflate(inflater, container, false)
18 | val root: View? = binding!!.getRoot()
19 | return root
20 | }
21 |
22 | override fun onDestroyView() {
23 | super.onDestroyView()
24 | binding = null
25 | }
26 |
27 |
28 | override fun onStart() {
29 | super.onStart()
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/util/ThemeUtil.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.util
2 |
3 | import android.content.Context
4 | import androidx.annotation.StyleRes
5 | import androidx.appcompat.app.AppCompatDelegate
6 | import com.google.android.material.color.DynamicColors
7 | import rikka.core.util.ResourceUtils
8 | import yangfentuozi.runner.App
9 | import yangfentuozi.runner.R
10 |
11 |
12 | object ThemeUtil {
13 | private val colorThemeMap: MutableMap = HashMap()
14 |
15 | const val MODE_NIGHT_FOLLOW_SYSTEM: String = "MODE_NIGHT_FOLLOW_SYSTEM"
16 | const val MODE_NIGHT_NO: String = "MODE_NIGHT_NO"
17 | const val MODE_NIGHT_YES: String = "MODE_NIGHT_YES"
18 |
19 | init {
20 | colorThemeMap.put("MATERIAL_RED", R.style.ThemeOverlay_MaterialRed)
21 | colorThemeMap.put("MATERIAL_PINK", R.style.ThemeOverlay_MaterialPink)
22 | colorThemeMap.put("MATERIAL_PURPLE", R.style.ThemeOverlay_MaterialPurple)
23 | colorThemeMap.put("MATERIAL_DEEP_PURPLE", R.style.ThemeOverlay_MaterialDeepPurple)
24 | colorThemeMap.put("MATERIAL_INDIGO", R.style.ThemeOverlay_MaterialIndigo)
25 | colorThemeMap.put("MATERIAL_BLUE", R.style.ThemeOverlay_MaterialBlue)
26 | colorThemeMap.put("MATERIAL_LIGHT_BLUE", R.style.ThemeOverlay_MaterialLightBlue)
27 | colorThemeMap.put("MATERIAL_CYAN", R.style.ThemeOverlay_MaterialCyan)
28 | colorThemeMap.put("MATERIAL_TEAL", R.style.ThemeOverlay_MaterialTeal)
29 | colorThemeMap.put("MATERIAL_GREEN", R.style.ThemeOverlay_MaterialGreen)
30 | colorThemeMap.put("MATERIAL_LIGHT_GREEN", R.style.ThemeOverlay_MaterialLightGreen)
31 | colorThemeMap.put("MATERIAL_LIME", R.style.ThemeOverlay_MaterialLime)
32 | colorThemeMap.put("MATERIAL_YELLOW", R.style.ThemeOverlay_MaterialYellow)
33 | colorThemeMap.put("MATERIAL_AMBER", R.style.ThemeOverlay_MaterialAmber)
34 | colorThemeMap.put("MATERIAL_ORANGE", R.style.ThemeOverlay_MaterialOrange)
35 | colorThemeMap.put("MATERIAL_DEEP_ORANGE", R.style.ThemeOverlay_MaterialDeepOrange)
36 | colorThemeMap.put("MATERIAL_BROWN", R.style.ThemeOverlay_MaterialBrown)
37 | colorThemeMap.put("MATERIAL_BLUE_GREY", R.style.ThemeOverlay_MaterialBlueGrey)
38 | }
39 |
40 | private const val THEME_DEFAULT = "DEFAULT"
41 | private const val THEME_BLACK = "BLACK"
42 |
43 | private val isBlackNightTheme: Boolean
44 | get() = App.preferences.getBoolean("black_dark_theme", false)
45 |
46 | val isSystemAccent: Boolean
47 | get() = DynamicColors.isDynamicColorAvailable() && App.preferences.getBoolean(
48 | "follow_system_accent",
49 | true
50 | )
51 |
52 | fun getNightTheme(context: Context): String {
53 | if (isBlackNightTheme
54 | && ResourceUtils.isNightMode(context.resources.configuration)
55 | ) return THEME_BLACK
56 |
57 | return THEME_DEFAULT
58 | }
59 |
60 | @StyleRes
61 | fun getNightThemeStyleRes(context: Context): Int {
62 | return when (getNightTheme(context)) {
63 | THEME_BLACK -> R.style.ThemeOverlay_Black
64 | THEME_DEFAULT -> R.style.ThemeOverlay
65 | else -> R.style.ThemeOverlay
66 | }
67 | }
68 |
69 | val colorTheme: String
70 | get() {
71 | if (isSystemAccent) {
72 | return "SYSTEM"
73 | }
74 | return App.preferences.getString("theme_color", "COLOR_BLUE")!!
75 | }
76 |
77 | @get:StyleRes
78 | val colorThemeStyleRes: Int
79 | get() {
80 | val theme = colorThemeMap[colorTheme]
81 | if (theme == null) {
82 | return R.style.ThemeOverlay_MaterialBlue
83 | }
84 | return theme
85 | }
86 |
87 | fun getDarkTheme(mode: String): Int {
88 | return when (mode) {
89 | MODE_NIGHT_FOLLOW_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
90 | MODE_NIGHT_YES -> AppCompatDelegate.MODE_NIGHT_YES
91 | MODE_NIGHT_NO -> AppCompatDelegate.MODE_NIGHT_NO
92 | else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
93 | }
94 | }
95 |
96 | val darkTheme: Int
97 | get() = getDarkTheme(
98 | App.preferences.getString(
99 | "dark_theme",
100 | MODE_NIGHT_FOLLOW_SYSTEM
101 | )!!
102 | )
103 | }
--------------------------------------------------------------------------------
/app/src/main/java/yangfentuozi/runner/util/ThrowableUtil.kt:
--------------------------------------------------------------------------------
1 | package yangfentuozi.runner.util
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.os.Looper
6 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
7 | import yangfentuozi.runner.R
8 |
9 | object ThrowableUtil {
10 |
11 | fun Throwable.toErrorDialog(context: Activity) {
12 | stackTraceToString().toErrorDialog(context)
13 | }
14 |
15 | fun CharSequence.toErrorDialog(context: Activity) {
16 | if (Looper.getMainLooper() == Looper.myLooper()) {
17 | createDialog(context, this)
18 | } else {
19 | context.runOnUiThread { createDialog(context, this) }
20 | }
21 | }
22 |
23 | private fun createDialog(context: Context, errorMsg: CharSequence) {
24 | MaterialAlertDialogBuilder(context)
25 | .setTitle(R.string.error)
26 | .setMessage(errorMsg)
27 | .show()
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/res/color-night/home_card_background_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/color/home_card_background_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_backup_restore_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_circle_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_configs_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dark_mode_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_empty_icon_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_error_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_format_color_fill_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_help_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home_baseline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_import_export_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_invert_colors_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_language_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | -
24 |
27 |
28 | -
32 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_layers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_layers_baseline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_layers_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_merge_type_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_palette_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_arrow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_arrow_baseline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_arrow_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_restore_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_run_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_applications_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_baseline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stop_circle_outline_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_terminal_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_circle_icon_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_crash_report.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
22 |
23 |
24 |
25 |
31 |
32 |
36 |
37 |
41 |
42 |
46 |
47 |
53 |
54 |
61 |
62 |
63 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_env_manage.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
22 |
23 |
24 |
25 |
30 |
31 |
36 |
37 |
44 |
45 |
46 |
47 |
48 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
23 |
24 |
32 |
33 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_stream_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
23 |
24 |
29 |
30 |
33 |
34 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_about.xml:
--------------------------------------------------------------------------------
1 |
20 |
26 |
27 |
32 |
33 |
38 |
39 |
45 |
46 |
51 |
52 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_add.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
18 |
19 |
23 |
24 |
29 |
30 |
31 |
32 |
37 |
38 |
43 |
44 |
45 |
46 |
50 |
51 |
56 |
57 |
62 |
63 |
64 |
69 |
70 |
75 |
76 |
77 |
78 |
79 |
85 |
86 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_edit_env.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
15 |
16 |
21 |
22 |
23 |
24 |
30 |
31 |
36 |
37 |
42 |
43 |
44 |
45 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_edit_env_e.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_edit_env_e2.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_exec.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
23 |
24 |
27 |
28 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_import.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_pick_backup.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
23 |
24 |
30 |
31 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_proc.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
31 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_runner.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
19 |
26 |
27 |
28 |
29 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_terminal.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/home_item_container.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/home_service_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
23 |
24 |
32 |
33 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/home_shizuku_perm_request.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
19 |
20 |
27 |
28 |
29 |
30 |
39 |
40 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/home_shizuku_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
23 |
24 |
32 |
33 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/home_term_ext_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
21 |
27 |
28 |
36 |
37 |
44 |
45 |
46 |
47 |
48 |
56 |
57 |
64 |
65 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_cmd.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
21 |
22 |
33 |
34 |
43 |
44 |
45 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_env.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
24 |
25 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_env_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
24 |
25 |
26 |
27 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_proc.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
21 |
22 |
33 |
34 |
43 |
44 |
45 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/navigation/mobile_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
25 |
26 |
31 |
32 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/resources.properties:
--------------------------------------------------------------------------------
1 | unqualifiedResLocale=en-US
--------------------------------------------------------------------------------
/app/src/main/res/values-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp/values.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | androidx.recyclerview.widget.StaggeredGridLayoutManager
4 | 4
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w1240dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 200dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | - @string/dark_theme_off
4 | - @string/dark_theme_on
5 | - @string/dark_theme_follow_system
6 |
7 |
8 |
9 | - MODE_NIGHT_NO
10 | - MODE_NIGHT_YES
11 | - MODE_NIGHT_FOLLOW_SYSTEM
12 |
13 |
14 |
15 | - MATERIAL_RED
16 | - MATERIAL_PINK
17 | - MATERIAL_PURPLE
18 | - MATERIAL_DEEP_PURPLE
19 | - MATERIAL_INDIGO
20 | - MATERIAL_BLUE
21 | - MATERIAL_LIGHT_BLUE
22 | - MATERIAL_CYAN
23 | - MATERIAL_TEAL
24 | - MATERIAL_GREEN
25 | - MATERIAL_LIGHT_GREEN
26 | - MATERIAL_LIME
27 | - MATERIAL_YELLOW
28 | - MATERIAL_AMBER
29 | - MATERIAL_ORANGE
30 | - MATERIAL_DEEP_ORANGE
31 | - MATERIAL_BROWN
32 | - MATERIAL_BLUE_GREY
33 |
34 |
35 |
36 | - @string/color_red
37 | - @string/color_pink
38 | - @string/color_purple
39 | - @string/color_deep_purple
40 | - @string/color_indigo
41 | - @string/color_blue
42 | - @string/color_light_blue
43 | - @string/color_cyan
44 | - @string/color_teal
45 | - @string/color_green
46 | - @string/color_light_green
47 | - @string/color_lime
48 | - @string/color_yellow
49 | - @string/color_amber
50 | - @string/color_orange
51 | - @string/color_deep_orange
52 | - @string/color_brown
53 | - @string/color_blue_grey
54 |
55 |
56 |
57 | - @string/stable
58 | - @string/beta
59 |
60 |
61 |
62 | - CHANNEL_STABLE
63 | - CHANNEL_BETA
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 | 0dp
4 | 16dp
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
15 |
16 |
17 |
18 |
23 |
24 |
30 |
31 |
36 |
37 |
41 |
42 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/values/theme.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
23 |
24 |
25 |
26 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes_custom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes_overlay.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes_override.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
36 |
37 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/values/values.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | androidx.recyclerview.widget.LinearLayoutManager
4 | 2
5 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preference_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
12 |
13 |
19 |
20 |
26 |
27 |
33 |
39 |
40 |
41 |
42 |
50 |
51 |
52 |
53 |
59 |
67 |
75 |
80 |
81 |
82 |
83 |
89 |
90 |
96 |
97 |
98 |
99 |
100 |
105 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id("com.android.application") version "8.10.0" apply false
4 | id("org.jetbrains.kotlin.android") version "2.1.20" apply false
5 | id("dev.rikka.tools.materialthemebuilder") version "1.5.1"
6 | }
7 |
8 | val gitCommitId: String = listOf("git", "rev-parse", "--short", "HEAD").execute(project.rootDir).trim()
9 | val gitCommitCount: Int = listOf("git", "rev-list", "--count", "HEAD").execute(project.rootDir).trim().toInt()
10 | val baseVersionName = "1.0.0-rc1"
11 |
12 | extra.apply {
13 | set("compileSdk", 35)
14 | set("minSdk", 24)
15 | set("targetSdk", 35)
16 | set("versionCode", gitCommitCount)
17 | set("versionName", "${baseVersionName}.r${gitCommitCount}.${gitCommitId}")
18 | }
19 |
20 | fun List.execute(workingDir: File): String {
21 | return try {
22 | ProcessBuilder(this)
23 | .directory(workingDir)
24 | .redirectOutput(ProcessBuilder.Redirect.PIPE)
25 | .redirectError(ProcessBuilder.Redirect.PIPE)
26 | .start()
27 | .inputStream.bufferedReader().use { it.readText() }
28 | } catch (e: Exception) {
29 | logger.warn("Failed to execute git command: ${e.message}")
30 | "unknown" // fallback value
31 | }
32 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
22 | android.injected.testOnly=false
23 | enableLogger=true
24 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangFenTuoZi/Runner/d392ee62099419726f2cf01ab188d887dc1bc3aa/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Apr 19 02:04:56 CST 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | mavenLocal()
7 | }
8 | }
9 | dependencyResolutionManagement {
10 | repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
11 | repositories {
12 | google()
13 | mavenCentral()
14 | mavenLocal()
15 | maven { url = uri("https://jitpack.io") }
16 | }
17 | }
18 |
19 | rootProject.name = "Runner"
20 | include(":app")
--------------------------------------------------------------------------------