package com.zy.asrs.service.impl; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.baomidou.mybatisplus.service.impl.ServiceImpl; import com.zy.asrs.entity.TvDevice; import com.zy.asrs.mapper.TvDeviceMapper; import com.zy.asrs.service.TvDeviceService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Date; import java.util.List; /** * 电视机设备Service实现类 */ @Service public class TvDeviceServiceImpl extends ServiceImpl implements TvDeviceService { private static final Logger log = LoggerFactory.getLogger(TvDeviceServiceImpl.class); @Value("${adb.path}") private String adbPath; @Value("${adb.default-package:com.zy.monitor}") private String defaultPackage; @Override public String testConnection(Long id) throws Exception { TvDevice device = this.selectById(id); if (device == null) { throw new RuntimeException("设备不存在"); } StringBuilder result = new StringBuilder(); String adbAddress = device.getAdbAddress(); try { // 断开已有连接 executeAdbCommand("disconnect", adbAddress); Thread.sleep(500); // 尝试连接 String connectResult = executeAdbCommand("connect", adbAddress); result.append("连接: ").append(connectResult).append("\n"); // 检查连接状态 boolean connected = connectResult.contains("connected") && !connectResult.contains("failed"); if (connected) { // 获取设备信息 String devicesResult = executeAdbCommand("devices"); result.append("设备列表: ").append(devicesResult); // 更新设备状态 device.setStatus((short) 1); device.setLastConnectTime(new Date()); } else { device.setStatus((short) 0); } device.setUpdateTime(new Date()); this.updateById(device); return result.toString(); } catch (Exception e) { device.setStatus((short) 0); device.setUpdateTime(new Date()); this.updateById(device); throw e; } } @Override public int refreshAllStatus() { List devices = this.selectList(null); int count = 0; for (TvDevice device : devices) { try { testConnection(device.getId()); count++; } catch (Exception e) { log.error("测试设备 {} 连接失败: {}", device.getName(), e.getMessage()); } } return count; } @Override public String installApk(Long deviceId, String apkPath) throws Exception { TvDevice device = this.selectById(deviceId); if (device == null) { throw new RuntimeException("设备不存在"); } File apkFile = new File(apkPath); if (!apkFile.exists()) { throw new RuntimeException("APK文件不存在: " + apkPath); } StringBuilder result = new StringBuilder(); String adbAddress = device.getAdbAddress(); // 先连接设备 String connectResult = executeAdbCommand("connect", adbAddress); result.append("连接: ").append(connectResult).append("\n"); if (connectResult.contains("failed")) { device.setStatus((short) 0); device.setUpdateTime(new Date()); this.updateById(device); throw new RuntimeException("连接设备失败: " + connectResult); } // 等待连接稳定 Thread.sleep(1000); // 安装APK String installResult = executeAdbCommand("-s", adbAddress, "install", "-r", apkPath); result.append("安装: ").append(installResult); // 更新设备状态 device.setStatus((short) 1); device.setLastConnectTime(new Date()); device.setUpdateTime(new Date()); this.updateById(device); log.info("设备 {} 安装APK结果: {}", device.getName(), installResult); return result.toString(); } @Override public List batchInstallApk(List deviceIds, String apkPath) { List results = new ArrayList<>(); for (Long deviceId : deviceIds) { TvDevice device = this.selectById(deviceId); String deviceName = device != null ? device.getName() : "ID:" + deviceId; try { String result = installApk(deviceId, apkPath); results.add(deviceName + ": 安装成功\n" + result); } catch (Exception e) { results.add(deviceName + ": 安装失败 - " + e.getMessage()); } } return results; } @Override public List getOnlineDevices() { return this.selectList(new EntityWrapper().eq("status", 1)); } @Override public String launchApp(Long deviceId, String packageName) throws Exception { TvDevice device = this.selectById(deviceId); if (device == null) { throw new RuntimeException("设备不存在"); } // 使用默认包名如果未指定 String pkg = (packageName != null && !packageName.trim().isEmpty()) ? packageName.trim() : defaultPackage; StringBuilder result = new StringBuilder(); String adbAddress = device.getAdbAddress(); // 先连接设备 String connectResult = executeAdbCommand("connect", adbAddress); result.append("连接: ").append(connectResult).append("\n"); if (connectResult.contains("failed")) { device.setStatus((short) 0); device.setUpdateTime(new Date()); this.updateById(device); throw new RuntimeException("连接设备失败: " + connectResult); } // 等待连接稳定 Thread.sleep(500); // 方法1: 使用cmd package resolve-activity获取启动Activity(Android 7.0+) String launchResult = null; boolean launched = false; try { // 尝试使用dumpsys获取启动Activity String dumpResult = executeAdbCommand("-s", adbAddress, "shell", "cmd", "package", "resolve-activity", "--brief", pkg); if (dumpResult != null && dumpResult.contains("/")) { // 解析出Activity名称 String[] lines = dumpResult.split("\n"); for (String line : lines) { line = line.trim(); if (line.contains("/") && !line.startsWith("priority") && !line.startsWith("#")) { // 找到类似 com.zy.app/.MainActivity 的格式 launchResult = executeAdbCommand("-s", adbAddress, "shell", "am", "start", "-n", line); launched = true; break; } } } } catch (Exception e) { log.warn("cmd package方式失败,尝试备用方式: {}", e.getMessage()); } // 方法2: 如果方法1失败,使用am start -a android.intent.action.MAIN if (!launched) { launchResult = executeAdbCommand("-s", adbAddress, "shell", "am", "start", "-a", "android.intent.action.MAIN", "-c", "android.intent.category.LAUNCHER", "-n", pkg + "/.MainActivity"); // 如果还是失败,尝试常见的Activity名称 if (launchResult != null && launchResult.contains("Error")) { // 尝试 .ui.MainActivity launchResult = executeAdbCommand("-s", adbAddress, "shell", "am", "start", "-a", "android.intent.action.MAIN", "-c", "android.intent.category.LAUNCHER", "--package", pkg); } } result.append("启动: ").append(launchResult); // 检查是否启动成功 if (launchResult != null && (launchResult.contains("Starting:") || launchResult.contains("cmp="))) { log.info("设备 {} 启动应用 {} 成功", device.getName(), pkg); } else { log.warn("设备 {} 启动应用 {} 可能失败: {}", device.getName(), pkg, launchResult); } // 更新设备状态 device.setStatus((short) 1); device.setLastConnectTime(new Date()); device.setUpdateTime(new Date()); this.updateById(device); return result.toString(); } @Override public List batchLaunchApp(List deviceIds, String packageName) { List results = new ArrayList<>(); for (Long deviceId : deviceIds) { TvDevice device = this.selectById(deviceId); String deviceName = device != null ? device.getName() : "ID:" + deviceId; try { String result = launchApp(deviceId, packageName); results.add(deviceName + ": 启动成功\n" + result); } catch (Exception e) { results.add(deviceName + ": 启动失败 - " + e.getMessage()); } } return results; } @Override public String captureScreen(Long deviceId) throws Exception { TvDevice device = this.selectById(deviceId); if (device == null) { throw new RuntimeException("设备不存在"); } String adbAddress = device.getAdbAddress(); // 先确保连接 String connectResult = executeAdbCommand("connect", adbAddress); if (connectResult.contains("failed")) { device.setStatus((short) 0); device.setUpdateTime(new Date()); this.updateById(device); throw new RuntimeException("连接设备失败: " + connectResult); } // 使用exec-out直接获取PNG二进制数据 List command = new ArrayList<>(); command.add(adbPath); command.add("-s"); command.add(adbAddress); command.add("exec-out"); command.add("screencap"); command.add("-p"); log.info("执行截图命令: {}", String.join(" ", command)); ProcessBuilder pb = new ProcessBuilder(command); Process process = pb.start(); // 读取二进制PNG数据 ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream is = process.getInputStream(); byte[] buffer = new byte[8192]; int len; while ((len = is.read(buffer)) != -1) { baos.write(buffer, 0, len); } int exitCode = process.waitFor(); byte[] pngData = baos.toByteArray(); if (exitCode != 0 || pngData.length < 100) { // 读取错误信息 BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); StringBuilder errorMsg = new StringBuilder(); String line; while ((line = errorReader.readLine()) != null) { errorMsg.append(line); } log.error("截图失败,退出码: {}, 数据长度: {}, 错误: {}", exitCode, pngData.length, errorMsg); throw new RuntimeException("截图失败: " + errorMsg); } // 更新设备状态 device.setStatus((short) 1); device.setLastConnectTime(new Date()); device.setUpdateTime(new Date()); this.updateById(device); // 转换为Base64 String base64Image = Base64.getEncoder().encodeToString(pngData); log.info("设备 {} 截图成功,图片大小: {} bytes", device.getName(), pngData.length); return base64Image; } /** * 执行ADB命令 */ private String executeAdbCommand(String... args) throws Exception { List command = new ArrayList<>(); command.add(adbPath); command.addAll(Arrays.asList(args)); log.info("执行ADB命令: {}", String.join(" ", command)); ProcessBuilder pb = new ProcessBuilder(command); pb.redirectErrorStream(true); Process process = pb.start(); StringBuilder output = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { output.append(line).append("\n"); } } int exitCode = process.waitFor(); String result = output.toString().trim(); if (exitCode != 0 && !result.contains("Success") && !result.contains("connected")) { log.warn("ADB命令执行返回码: {}, 输出: {}", exitCode, result); } return result; } }