From 61ca0d267245ed631b4110992249914a4d4cbb7e Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 19 Jan 2026 14:43:50 +0800 Subject: [PATCH 01/44] =?UTF-8?q?Termux=E5=BA=94=E7=94=A8=E5=8C=85?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E8=B0=83=E7=94=A8=E6=88=90=E5=8A=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- winboll/build.gradle | 22 +- winboll/build.properties | 4 +- winboll/src/main/AndroidManifest.xml | 10 +- .../winboll/termux/TermuxCommandExecutor.java | 177 +++++++++ .../unittest/TermuxEnvTestActivity.java | 353 ++++++++++++++---- .../res/layout/activity_termux_env_test.xml | 5 +- 6 files changed, 496 insertions(+), 75 deletions(-) create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/termux/TermuxCommandExecutor.java diff --git a/winboll/build.gradle b/winboll/build.gradle index dc782b2..a06fee2 100644 --- a/winboll/build.gradle +++ b/winboll/build.gradle @@ -73,12 +73,13 @@ dependencies { implementation 'com.alibaba:fastjson:1.2.76' // AndroidX 类库 - api 'androidx.appcompat:appcompat:1.1.0' + /*api 'androidx.appcompat:appcompat:1.1.0' //api 'com.google.android.material:material:1.4.0' //api 'androidx.viewpager:viewpager:1.0.0' //api 'androidx.vectordrawable:vectordrawable:1.1.0' //api 'androidx.vectordrawable:vectordrawable-animated:1.1.0' - //api 'androidx.fragment:fragment:1.1.0' + //api 'androidx.fragment:fragment:1.1.0'*/ + // 米盟 api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk @@ -88,6 +89,23 @@ dependencies { api 'com.google.code.gson:gson:2.8.5' api 'com.github.bumptech.glide:glide:4.9.0' //annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' + + implementation "androidx.annotation:annotation:1.3.0" + implementation "androidx.core:core:1.6.0" + implementation "androidx.drawerlayout:drawerlayout:1.1.1" + implementation "androidx.preference:preference:1.1.1" + implementation "androidx.viewpager:viewpager:1.0.0" + implementation "com.google.android.material:material:1.4.0" + implementation "com.google.guava:guava:24.1-jre" + /* + implementation "io.noties.markwon:core:$markwonVersion" + implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" + implementation "io.noties.markwon:linkify:$markwonVersion" + implementation "io.noties.markwon:recycler:$markwonVersion" + */ + implementation 'com.termux:terminal-emulator:0.118.0' + implementation 'com.termux:terminal-view:0.118.0' + implementation 'com.termux:termux-shared:0.118.0' // WinBoLL库 nexus.winboll.cc 地址 api 'cc.winboll.studio:libaes:15.15.7' diff --git a/winboll/build.properties b/winboll/build.properties index 1ddcdbd..5849bd7 100644 --- a/winboll/build.properties +++ b/winboll/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Jan 19 03:57:58 GMT 2026 +#Mon Jan 19 06:42:29 GMT 2026 stageCount=11 libraryProject= baseVersion=15.11 publishVersion=15.11.10 -buildCount=12 +buildCount=25 baseBetaVersion=15.11.11 diff --git a/winboll/src/main/AndroidManifest.xml b/winboll/src/main/AndroidManifest.xml index 6fac454..74b3ee5 100644 --- a/winboll/src/main/AndroidManifest.xml +++ b/winboll/src/main/AndroidManifest.xml @@ -2,7 +2,8 @@ + xmlns:tools="http://schemas.android.com/tools" + android:sharedUserId="com.termux"> @@ -12,7 +13,12 @@ - + + + + + + * @Date 2026/01/19 16:30:00 + * @LastEditTime 2026/01/20 10:15:00 + */ +public class TermuxCommandExecutor { + private static final String TAG = "TermuxCommandExecutor"; + // 核心修复:Termux 官方包名(无 .app 后缀) + private static final String TERMUX_PACKAGE_NAME = "com.termux"; + // Termux RunCommandService 完整类名(包名+类名) + private static final String TERMUX_RUN_CMD_SERVICE_CLASS = "com.termux.app.RunCommandService"; + private static final String TERMUX_RUN_CMD_ACTION = TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND; + + /** + * 执行 Termux 命令(核心方法) + * @param context 上下文(如 Activity、Service) + * @param command 要执行的命令路径(如 "/bin/ls"、"/data/data/com.termux/files/usr/bin/bash") + * @param args 命令参数(如 ["-l", "/data/data/com.termux/files/home"]) + * @param workDir 工作目录(可为 null,默认 Termux 主目录) + * @param isBackground 是否后台执行(true=后台,false=终端会话执行) + * @param resultDir 命令结果输出目录(可为 null,不输出到文件) + * @return 是否成功发送命令请求 + */ + public static boolean executeCommand(Context context, String command, String[] args, String workDir, boolean isBackground, String resultDir) { + // 1. 校验上下文和命令合法性 + if (context == null || command == null || command.isEmpty()) { + LogUtils.e(TAG, "执行命令失败:上下文或命令为空"); + return false; + } + + // 2. 校验 Termux 是否安装(新增:提前校验,避免白跑流程) + if (!isTermuxInstalled(context)) { + LogUtils.e(TAG, "执行命令失败:Termux 未安装"); + return false; + } + + // 3. 创建 Intent 并设置目标 Service + Intent intent = new Intent(TERMUX_RUN_CMD_ACTION); + intent.setClassName(TERMUX_PACKAGE_NAME, TERMUX_RUN_CMD_SERVICE_CLASS); // 用正确包名 + intent.setPackage(TERMUX_PACKAGE_NAME); // 明确包名,避免歧义 + + // 4. 设置核心命令参数(遵循 Termux RunCommandService 规范) + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH, command); + if (args != null && args.length > 0) { + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_ARGUMENTS, args); + LogUtils.d(TAG, "命令参数:" + String.join(",", args)); + } + if (workDir != null && !workDir.isEmpty()) { + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_WORKDIR, workDir); + LogUtils.d(TAG, "工作目录:" + workDir); + } + + // 5. 设置执行模式(后台/终端会话) + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_BACKGROUND, isBackground); + String runner = isBackground ? Runner.APP_SHELL.getName() : Runner.TERMINAL_SESSION.getName(); + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RUNNER, runner); + LogUtils.d(TAG, "执行模式:" + (isBackground ? "后台" : "终端会话") + ",Runner:" + runner); + + // 6. 设置命令结果输出(可选,输出到文件) + if (resultDir != null && !resultDir.isEmpty()) { + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_DIRECTORY, resultDir); + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_SINGLE_FILE, true); + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_FILE_BASENAME, "authcenter_cmd_result"); + LogUtils.d(TAG, "结果输出目录:" + resultDir); + } + + // 7. 允许替换参数中的逗号替代字符 + intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS, true); + + // 8. 发送请求(区分 Android O 及以上的前台服务) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent); + LogUtils.d(TAG, "Android O+ 启动前台服务发送命令"); + } else { + context.startService(intent); + LogUtils.d(TAG, "启动普通服务发送命令"); + } + LogUtils.i(TAG, "命令发送成功:command=" + command); + return true; + } catch (Exception e) { + LogUtils.e(TAG, "命令发送失败:" + e.getMessage(), e); + return false; + } + } + + /** + * 简化方法:执行 Termux 终端命令(默认工作目录,终端会话执行) + * @param context 上下文 + * @param command 命令(如 "ls -l /home"、"echo 'hello termux'") + * @return 是否成功发送 + */ + public static boolean executeTerminalCommand(Context context, String command) { + LogUtils.d(TAG, "调用 executeTerminalCommand,命令:" + command); + if (command == null || command.isEmpty()) { + LogUtils.e(TAG, "命令为空,执行失败"); + return false; + } + // 通过 bash 执行任意终端命令 + String[] args = {"-c", command}; + return executeCommand( + context, + "/data/data/com.termux/files/usr/bin/bash", // Termux 默认 bash 路径(正确) + args, + "/data/data/com.termux/files/home", // 默认工作目录 + false, // 终端会话执行(可见) + null // 不输出到文件 + ); + } + + /** + * 简化方法:后台执行 Termux 命令(无输出文件) + * @param context 上下文 + * @param command 命令路径 + * @param args 命令参数 + * @return 是否成功发送 + */ + public static boolean executeBackgroundCommand(Context context, String command, String[] args) { + LogUtils.d(TAG, "调用 executeBackgroundCommand,command=" + command); + return executeCommand( + context, + command, + args, + null, + true, // 后台执行 + null + ); + } + + /** + * 校验 Termux 是否安装(修复核心错误) + * @param context 上下文 + * @return Termux 是否已安装 + */ + public static boolean isTermuxInstalled(Context context) { + LogUtils.d(TAG, "校验 Termux 是否安装,包名:" + TERMUX_PACKAGE_NAME); + if (context == null) { + LogUtils.e(TAG, "校验失败:上下文为空"); + return false; + } + try { + // 用正确的 Termux 包名查询(com.termux) + context.getPackageManager().getPackageInfo(TERMUX_PACKAGE_NAME, PackageManager.GET_ACTIVITIES); + LogUtils.d(TAG, "Termux 已安装"); + return true; + } catch (PackageManager.NameNotFoundException e) { + LogUtils.w(TAG, "Termux 未安装:" + e.getMessage()); + return false; + } catch (Exception e) { + LogUtils.e(TAG, "校验 Termux 安装状态异常:" + e.getMessage(), e); + return false; + } + } + + /** + * 校验 Termux 是否允许外部应用调用 + * @return 校验提示信息 + */ + public static String checkTermuxExternalAppPermission() { + String tip = "请确保 Termux 已开启「允许外部应用调用」权限:\n1. 打开 Termux 输入:termux-setup-storage\n2. 编辑配置文件:echo \"allow-external-apps = true\" > ~/.termux/termux.properties\n3. 重启 Termux 生效"; + LogUtils.d(TAG, "外部应用调用权限提示:" + tip); + return tip; + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TermuxEnvTestActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TermuxEnvTestActivity.java index 94c432a..a8fa62f 100644 --- a/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TermuxEnvTestActivity.java +++ b/winboll/src/main/java/cc/winboll/studio/winboll/unittest/TermuxEnvTestActivity.java @@ -1,33 +1,43 @@ package cc.winboll.studio.winboll.unittest; -import android.content.Intent; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.view.View; import android.widget.TextView; -import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.winboll.MainActivity; import cc.winboll.studio.winboll.R; import cc.winboll.studio.winboll.activities.BaseWinBoLLActivity; +import cc.winboll.studio.winboll.termux.TermuxCommandExecutor; +import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; /** * @Author 豆包&ZhanGSKen - * @Date 2026/01/19 11:11 - * @LastEditTime 2026/01/19 15:30 - * @Describe Termux 环境测试(新增:读取Termux主目录文件列表功能) + * @Date 2026/01/19 11:11:00 + * @LastEditTime 2026/01/20 15:30:00 + * @Describe Termux环境测试工具(跨包+sharedUserId模式) + * 适配不同应用包名(当前包:cc.winboll.studio.winboll.beta / Termux包:com.termux) + * 支持Termux目录读取、命令执行及结果展示,基于sharedUserId实现跨包权限适配 */ public class TermuxEnvTestActivity extends BaseWinBoLLActivity { - + // 常量属性(置顶排列) public static final String TAG = "TermuxEnvTestActivity"; - // Termux主目录固定路径 private static final String TERMUX_HOME_PATH = "/data/data/com.termux/files/home/WinBoLLStudio"; + private static final String CMD_RESULT_FILE = TERMUX_HOME_PATH + "/.authcenter_cmd_result.tmp"; + // 成员属性(常量后排列) private Toolbar mToolbar; + private TextView tvMessage; + private Handler mainHandler; @Override public String getTag() { @@ -37,111 +47,320 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + LogUtils.d(TAG, "onCreate() 调用,初始化Activity"); setContentView(R.layout.activity_termux_env_test); + initView(); initToolbar(); + initTermuxDirectory(); + LogUtils.d(TAG, "onCreate() 执行完成"); } + /** + * 初始化视图组件 + */ + private void initView() { + LogUtils.d(TAG, "initView() 开始初始化视图"); + tvMessage = (TextView) findViewById(R.id.tv_message); + mainHandler = new Handler(Looper.getMainLooper()); + + // 初始化提示信息 + StringBuilder initMsg = new StringBuilder(); + initMsg.append("Termux 测试工具(跨包+sharedUserId模式)\n"); + initMsg.append("-------------------------\n"); + initMsg.append("当前应用包名:").append(getPackageName()).append("\n"); + initMsg.append("Termux应用包名:com.termux\n"); + initMsg.append("sharedUserId:com.termux(需一致)\n"); + initMsg.append("-------------------------\n"); + tvMessage.setText(initMsg.toString()); + + LogUtils.d(TAG, "initView() 初始化完成,tvMessage初始值:" + initMsg.toString().trim()); + } + + /** + * 初始化Toolbar组件 + */ private void initToolbar() { - LogUtils.d(TAG, "initToolbar() 开始初始化"); + LogUtils.d(TAG, "initToolbar() 开始初始化Toolbar"); mToolbar = (Toolbar) findViewById(R.id.toolbar); + if (mToolbar == null) { - LogUtils.e(TAG, "initToolbar() | Toolbar未找到"); + LogUtils.e(TAG, "initToolbar() 错误:未找到Toolbar组件"); return; } + setSupportActionBar(mToolbar); mToolbar.setSubtitle(getTag()); ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "导航栏 点击返回按钮"); + LogUtils.d(TAG, "initToolbar() 导航栏返回按钮点击"); getActivity().finish(); WinBoLLActivityManager.getInstance().startWinBoLLActivity(getActivity(), MainActivity.class); } }); - LogUtils.d(TAG, "initToolbar() 配置完成"); + + LogUtils.d(TAG, "initToolbar() 初始化完成"); } /** - * 核心功能:读取Termux主目录文件列表,返回字符串格式 - * @return 目录路径+文件列表(换行分隔),异常时返回错误信息 + * 初始化Termux目标目录(跨包+sharedUserId模式) */ - public String readTermuxHomeFileList() { - LogUtils.d(TAG, "开始读取Termux目录:" + TERMUX_HOME_PATH); + private void initTermuxDirectory() { + LogUtils.d(TAG, "initTermuxDirectory() 开始初始化Termux目录,路径:" + TERMUX_HOME_PATH); + File termuxDir = new File(TERMUX_HOME_PATH); + + if (termuxDir.exists()) { + LogUtils.d(TAG, "initTermuxDirectory() Termux目录已存在,无需创建"); + return; + } + + tvMessage.append("正在创建Termux目标目录...\n"); + boolean createSuccess = termuxDir.mkdirs(); + if (createSuccess) { + LogUtils.d(TAG, "initTermuxDirectory() Termux目录创建成功:" + TERMUX_HOME_PATH); + tvMessage.append("Termux目录创建成功:"); + tvMessage.append(TERMUX_HOME_PATH); + tvMessage.append("\n"); + } else { + LogUtils.e(TAG, "initTermuxDirectory() 错误:Termux目录创建失败"); + tvMessage.append("警告:Termux目录创建失败!\n请检查:\n1.AndroidManifest.xml中sharedUserId是否为com.termux\n2.设备是否已root(部分机型需root才能跨包写私有目录)\n"); + } + } + + /** + * 测试读取Termux目录(按钮点击事件) + */ + public void onTestTermuxEnv(View view) { + LogUtils.d(TAG, "onTestTermuxEnv() 按钮点击,开始读取Termux目录"); + tvMessage.append("\n【测试:读取Termux目录】\n"); + + String fileListStr = readTermuxHomeFileList(); + tvMessage.append(fileListStr); + tvMessage.append("\n-------------------------\n"); + + LogUtils.d(TAG, "onTestTermuxEnv() 执行完成,读取结果长度:" + fileListStr.length() + "字符"); + } + + /** + * 测试执行Termux命令(按钮点击事件) + */ + public void onTestTermuxCMD(View view) { + LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击,开始执行Termux命令"); + tvMessage.append("\n【测试:执行Termux命令】\n"); + + // 1. 校验Termux是否安装 + if (!TermuxCommandExecutor.isTermuxInstalled(this)) { + LogUtils.e(TAG, "onTestTermuxCMD() 错误:未安装Termux应用"); + tvMessage.append("错误:未安装Termux应用(包名:com.termux)\n"); + return; + } + + // 2. 校验目录是否存在 + File termuxDir = new File(TERMUX_HOME_PATH); + if (!termuxDir.exists()) { + LogUtils.e(TAG, "onTestTermuxCMD() 错误:Termux目录不存在,路径:" + TERMUX_HOME_PATH); + tvMessage.append("错误:Termux目录不存在,无法执行命令\n"); + return; + } + + // 3. 构造并执行命令(修复:用bash -c包裹多命令,确保输出统一重定向) + tvMessage.append("正在执行命令...\n"); + String targetCmd = "ls -al " + TERMUX_HOME_PATH + " && echo '\\n系统信息:' && uname -a && echo '\\n当前用户:' && whoami"; + // 关键修复:用bash -c包裹命令,确保所有输出被重定向 + String cmdWithRedirect = "bash -c \"" + targetCmd + "\" > " + CMD_RESULT_FILE + " 2>&1"; + LogUtils.d(TAG, "onTestTermuxCMD() 执行命令:" + cmdWithRedirect); + + boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, cmdWithRedirect); + if (!cmdSuccess) { + LogUtils.e(TAG, "onTestTermuxCMD() 错误:命令发送失败"); + tvMessage.append("命令发送失败!\n可能原因:\n1.Termux未开启外部应用调用权限\n2.sharedUserId配置不一致\n3.TermuxCommandExecutor类路径错误\n"); + return; + } + + // 4. 延迟读取结果(调整延迟为2.5秒,适配命令执行耗时) + new Thread(new Runnable() { + @Override + public void run() { + LogUtils.d(TAG, "onTestTermuxCMD() 结果读取线程启动,延迟2.5秒"); + try { + Thread.sleep(2500); + final String result = readCmdResultDirectly(); + + mainHandler.post(new Runnable() { + @Override + public void run() { + tvMessage.append("命令执行结果:\n"); + tvMessage.append(result); + tvMessage.append("\n-------------------------\n"); + deleteCmdResultFile(); + } + }); + } catch (InterruptedException e) { + LogUtils.e(TAG, "onTestTermuxCMD() 错误:结果读取线程中断", e); + mainHandler.post(new Runnable() { + @Override + public void run() { + tvMessage.append("错误:读取结果失败,线程被中断\n"); + } + }); + } + } + }).start(); + } + + /** + * 跨包读取Termux命令结果文件 + * @return 命令执行结果字符串 + */ + private String readCmdResultDirectly() { + LogUtils.d(TAG, "readCmdResultDirectly() 开始读取命令结果,文件路径:" + CMD_RESULT_FILE); + File resultFile = new File(CMD_RESULT_FILE); + + // 校验文件存在性 + if (!resultFile.exists()) { + LogUtils.e(TAG, "readCmdResultDirectly() 错误:结果文件不存在"); + return "错误:结果文件不存在\n可能原因:\n1.命令执行失败\n2.延迟时间不足\n3.跨包写权限被拒绝(需root)"; + } + + // 校验文件可读性 + if (!resultFile.canRead()) { + LogUtils.e(TAG, "readCmdResultDirectly() 错误:无结果文件读取权限"); + return "错误:无结果文件读取权限(sharedUserId配置错误或未root)"; + } + + // 读取文件内容 + StringBuilder result = new StringBuilder(); + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(new FileInputStream(resultFile), StandardCharsets.UTF_8)); + String line; + while ((line = br.readLine()) != null) { + result.append(line).append("\n"); + } + LogUtils.d(TAG, "readCmdResultDirectly() 文件读取完成,结果长度:" + result.length() + "字符"); + } catch (IOException e) { + LogUtils.e(TAG, "readCmdResultDirectly() 错误:文件读取异常", e); + return "错误:读取文件异常 → " + e.getMessage(); + } finally { + if (br != null) { + try { + br.close(); + LogUtils.d(TAG, "readCmdResultDirectly() BufferedReader资源已关闭"); + } catch (IOException e) { + LogUtils.e(TAG, "readCmdResultDirectly() 错误:BufferedReader关闭异常", e); + } + } + } + + String resultStr = result.toString().trim(); + return resultStr.isEmpty() ? "命令执行成功,但无输出" : resultStr; + } + + /** + * 删除命令结果临时文件 + */ + private void deleteCmdResultFile() { + LogUtils.d(TAG, "deleteCmdResultFile() 开始删除临时文件,路径:" + CMD_RESULT_FILE); + File resultFile = new File(CMD_RESULT_FILE); + + if (!resultFile.exists()) { + LogUtils.d(TAG, "deleteCmdResultFile() 临时文件不存在,无需删除"); + return; + } + + if (resultFile.canWrite()) { + boolean deleteSuccess = resultFile.delete(); + if (deleteSuccess) { + LogUtils.d(TAG, "deleteCmdResultFile() 临时文件删除成功"); + } else { + LogUtils.w(TAG, "deleteCmdResultFile() 警告:临时文件删除失败"); + tvMessage.append("警告:临时结果文件删除失败\n"); + } + } else { + LogUtils.w(TAG, "deleteCmdResultFile() 警告:无临时文件删除权限"); + } + } + + /** + * 跨包读取Termux目录文件列表 + * @return 目录文件列表字符串 + */ + private String readTermuxHomeFileList() { + LogUtils.d(TAG, "readTermuxHomeFileList() 开始读取目录,路径:" + TERMUX_HOME_PATH); File termuxHomeDir = new File(TERMUX_HOME_PATH); StringBuilder result = new StringBuilder(); - // 1. 检查目录是否存在 + // 基础校验 if (!termuxHomeDir.exists()) { - String errorMsg = "错误:Termux目录不存在 → " + TERMUX_HOME_PATH; - LogUtils.e(TAG, errorMsg); - return errorMsg; + LogUtils.e(TAG, "readTermuxHomeFileList() 错误:目录不存在"); + return "错误:Termux目录不存在 → " + TERMUX_HOME_PATH; } - - // 2. 检查是否为目录 if (!termuxHomeDir.isDirectory()) { - String errorMsg = "错误:指定路径不是目录 → " + TERMUX_HOME_PATH; - LogUtils.e(TAG, errorMsg); - return errorMsg; + LogUtils.e(TAG, "readTermuxHomeFileList() 错误:指定路径不是目录"); + return "错误:指定路径不是目录 → " + TERMUX_HOME_PATH; } - - // 3. 检查读写权限 if (!termuxHomeDir.canRead()) { - String errorMsg = "错误:无目录读取权限 → " + TERMUX_HOME_PATH; - LogUtils.e(TAG, errorMsg); - return errorMsg; + LogUtils.e(TAG, "readTermuxHomeFileList() 错误:无目录读取权限"); + return "错误:无目录读取权限(需满足:1.sharedUserId=com.termux 2.设备root)"; } - // 4. 获取目录下所有文件/子目录 + // 获取文件列表 File[] files = termuxHomeDir.listFiles(); if (files == null || files.length == 0) { - String emptyMsg = "Termux目录为空 → " + TERMUX_HOME_PATH; - LogUtils.d(TAG, emptyMsg); - return emptyMsg; + LogUtils.d(TAG, "readTermuxHomeFileList() 目录为空"); + return "Termux目录为空 → " + TERMUX_HOME_PATH; } - // 5. 拼接文件列表字符串(包含类型、名称、路径) + // 拼接结果字符串 result.append("Termux主目录:").append(TERMUX_HOME_PATH).append("\n"); result.append("文件/子目录总数:").append(files.length).append("\n"); + result.append("目录权限:").append("读:").append(termuxHomeDir.canRead()).append(" | 写:").append(termuxHomeDir.canWrite()).append("\n"); result.append("-------------------------\n"); + for (File file : files) { String fileType = file.isDirectory() ? "[目录]" : "[文件]"; String fileName = file.getName(); String filePath = file.getAbsolutePath(); - result.append(fileType).append(" ").append(fileName).append(" → ").append(filePath).append("\n"); + String fileSize = file.isFile() ? " | 大小:" + formatFileSize(file.length()) : ""; + String filePerm = " | 权限:r:" + file.canRead() + "/w:" + file.canWrite(); + result.append(fileType).append(" ").append(fileName).append(fileSize).append(filePerm).append(" → ").append(filePath).append("\n"); } - // 6. 去除末尾多余换行 - String fileListStr = result.toString().trim(); - LogUtils.d(TAG, "Termux目录读取完成,结果长度:" + fileListStr.length()); - return fileListStr; + LogUtils.d(TAG, "readTermuxHomeFileList() 读取完成,结果长度:" + result.length() + "字符"); + return result.toString().trim(); + } + + /** + * 格式化文件大小(B→KB→MB) + * @param length 文件长度(字节) + * @return 格式化后的文件大小字符串 + */ + private String formatFileSize(long length) { + LogUtils.d(TAG, "formatFileSize() 调用,文件长度:" + length + "B"); + String sizeStr; + if (length < 1024) { + sizeStr = length + "B"; + } else if (length < 1024 * 1024) { + sizeStr = String.format("%.1fKB", length / 1024.0); + } else { + sizeStr = String.format("%.1fMB", length / (1024.0 * 1024)); + } + LogUtils.d(TAG, "formatFileSize() 格式化结果:" + sizeStr); + return sizeStr; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + LogUtils.d(TAG, "onDestroy() 调用,清理资源"); + deleteCmdResultFile(); + if (mainHandler != null) { + mainHandler.removeCallbacksAndMessages(null); + LogUtils.d(TAG, "onDestroy() Handler资源已释放"); + } + LogUtils.d(TAG, "onDestroy() 执行完成"); } - - public void onTestTermuxEnv(View view) { - TextView tvMessage = findViewById(R.id.tv_message); - // 测试:读取Termux目录文件列表并打印日志 - String fileListStr = readTermuxHomeFileList(); - tvMessage.append(fileListStr); - } - - public void onTestTermuxCMD(View view) { - // pkg update && pkg upgrade -y - // pkg install termux-api -y - - Intent intent = new Intent(); - intent.setPackage("com.termux"); - intent.setAction("com.termux.RUN_COMMAND"); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/home"); - intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{"ls"}); - intent.putExtra("com.termux.RUN_COMMAND_WAIT_FOR_RESULT", false); - intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false); - if (intent.resolveActivity(getPackageManager()) != null) { - startActivity(intent); - } else { - Toast.makeText(this, "Termux或Termux:API未正确安装", Toast.LENGTH_SHORT).show(); - } - - } } diff --git a/winboll/src/main/res/layout/activity_termux_env_test.xml b/winboll/src/main/res/layout/activity_termux_env_test.xml index ff442a5..9378f12 100644 --- a/winboll/src/main/res/layout/activity_termux_env_test.xml +++ b/winboll/src/main/res/layout/activity_termux_env_test.xml @@ -22,7 +22,7 @@ android:layout_height="wrap_content" android:text="TestTermuxCMD" android:onClick="onTestTermuxCMD"/> - +