#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import sys import subprocess import json import threading import tkinter as tk from tkinter import ttk, scrolledtext, filedialog, messagebox from pathlib import Path from urllib.request import urlopen, Request from urllib.error import URLError, HTTPError from datetime import datetime import zipfile try: import pyzipper except ImportError: pyzipper = None import shutil import time class ADKAPKGUI: def __init__(self): self.root = tk.Tk() self.root.title("长安逸动语言刷入工具") self.root.geometry("650x550") self.root.resizable(True, True) # 设置颜色主题 self.colors = { 'bg_dark': '#1e1e2e', 'bg_light': '#2a2a3e', 'accent': '#6c5ce7', 'accent_hover': '#5b4bc4', 'success': '#00b894', 'error': '#d63031', 'warning': '#fdcb6e', 'info': '#0984e3', 'text': '#dfe6e9', 'text_secondary': '#b2bec3', 'border': '#3d3d5e' } self.base_dir = Path(sys.executable).parent if getattr(sys, 'frozen', False) else Path(__file__).parent if getattr(sys, 'frozen', False): self.adb = str(Path(sys._MEIPASS) / 'adb.exe') self.sz = str(Path(sys._MEIPASS) / '7za.exe') else: self.adb = 'adb' self.sz = str(self.base_dir / '7za.exe') self.package_file = self.base_dir / "package.bin" self.extract_password = None self.apps_dir = None self.temp_dir = None self.api_url = "https://api.changan.softwindy.cn/api/authorizations/auth-check" self.vin = None self.device_connected = False self._refreshing = False # 防止并发刷新 # 设置样式 self.setup_styles() self.setup_ui() self.center_window() # 检查环境 self.check_environment() # 启动设备状态监控 self.start_device_monitor() def setup_styles(self): """设置自定义样式""" style = ttk.Style() style.theme_use('clam') # 配置主颜色 style.configure('TFrame', background=self.colors['bg_dark']) style.configure('TLabel', background=self.colors['bg_dark'], foreground=self.colors['text']) style.configure('TLabelframe', background=self.colors['bg_dark'], foreground=self.colors['text']) style.configure('TLabelframe.Label', background=self.colors['bg_dark'], foreground=self.colors['accent']) # 配置进度条 style.configure('TProgressbar', background=self.colors['accent'], troughcolor=self.colors['bg_light'], borderwidth=0) def setup_ui(self): """设置UI界面""" # 配置根窗口 self.root.configure(bg=self.colors['bg_dark']) # 创建主框架 main_frame = tk.Frame(self.root, bg=self.colors['bg_dark']) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 顶部标题栏 title_frame = tk.Frame(main_frame, bg=self.colors['bg_dark'], height=65) title_frame.pack(fill=tk.X, pady=(0, 10)) title_frame.pack_propagate(False) # 标题 title_label = tk.Label(title_frame, text="🚀 长安逸动多语言刷机", font=('Microsoft YaHei', 18, 'bold'), fg=self.colors['accent'], bg=self.colors['bg_dark']) title_label.pack() subtitle_label = tk.Label(title_frame, text="@港泊智行 - 出口改装一站式服务", font=('Microsoft YaHei', 9), fg=self.colors['text_secondary'], bg=self.colors['bg_dark']) subtitle_label.pack() # 按钮区域(两排,每排5个) button_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) button_frame.pack(fill=tk.X, pady=(0, 10), padx=5) # 按钮样式参数 btn_params = { 'font': ('Microsoft YaHei', 9), 'fg': 'white', 'relief': tk.FLAT, 'cursor': 'hand2', 'height': 1, 'width': 14 } # 第一排按钮 row1_frame = tk.Frame(button_frame, bg=self.colors['bg_light']) row1_frame.pack(pady=(8, 4)) self.btn_push = tk.Button(row1_frame, text="📦 刷入语言包", command=self.push_all_apks, bg=self.colors['accent'], **btn_params) self.btn_push.pack(side=tk.LEFT, padx=4) self.btn_install_all = tk.Button(row1_frame, text="📱 批量安装", command=self.install_all_apks, bg=self.colors['accent'], **btn_params) self.btn_install_all.pack(side=tk.LEFT, padx=4) self.btn_install_single = tk.Button(row1_frame, text="🎯 单个安装", command=self.install_single_apk, bg=self.colors['accent'], **btn_params) self.btn_install_single.pack(side=tk.LEFT, padx=4) self.btn_language = tk.Button(row1_frame, text="🌐 语言设置", command=self.open_language_quick_set, bg=self.colors['accent'], **btn_params) self.btn_language.pack(side=tk.LEFT, padx=4) self.btn_settings = tk.Button(row1_frame, text="⚙️ 安卓设置", command=self.open_android_settings, bg=self.colors['accent'], **btn_params) self.btn_settings.pack(side=tk.LEFT, padx=4) # 第二排按钮 row2_frame = tk.Frame(button_frame, bg=self.colors['bg_light']) row2_frame.pack(pady=(4, 8)) self.btn_timezone = tk.Button(row2_frame, text="⏰ 时区设置", command=self.open_timezone_settings, bg=self.colors['accent'], **btn_params) self.btn_timezone.pack(side=tk.LEFT, padx=4) self.btn_reboot = tk.Button(row2_frame, text="🔄 重启设备", command=self.reboot_device, bg=self.colors['warning'], **btn_params) self.btn_reboot.pack(side=tk.LEFT, padx=4) self.btn_clear = tk.Button(row2_frame, text="🗑 清空日志", command=self.clear_log, bg=self.colors['info'], **btn_params) self.btn_clear.pack(side=tk.LEFT, padx=4) self.btn_exit = tk.Button(row2_frame, text="❌ 禁用升级", command=self.on_disable_upgrade, bg=self.colors['error'], **btn_params) self.btn_exit.pack(side=tk.LEFT, padx=4) # 工厂模式提示 factory_hint_frame = tk.Frame(main_frame, bg=self.colors['bg_dark']) factory_hint_frame.pack(fill=tk.X, pady=(0, 3)) tk.Label(factory_hint_frame, text="🔧 工厂模式:拨号 *#*#888 ,密码:369875", font=('Microsoft YaHei', 8), fg=self.colors['warning'], bg=self.colors['bg_dark']).pack(side=tk.LEFT, padx=2) # 设备状态栏(横条) status_bar_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) status_bar_frame.pack(fill=tk.X, pady=(0, 5)) # 状态指示器 status_indicator_frame = tk.Frame(status_bar_frame, bg=self.colors['bg_light']) status_indicator_frame.pack(side=tk.LEFT, padx=10, pady=5) self.status_indicator = tk.Canvas(status_indicator_frame, width=10, height=10, bg=self.colors['bg_light'], highlightthickness=0) self.status_indicator.pack(side=tk.LEFT) self.status_dot = self.status_indicator.create_oval(2, 2, 8, 8, fill='#636e72') tk.Label(status_indicator_frame, text="设备:", font=('Microsoft YaHei', 9), fg=self.colors['text'], bg=self.colors['bg_light']).pack(side=tk.LEFT, padx=(5, 3)) self.device_status_label = tk.Label(status_indicator_frame, text="未检测", font=('Microsoft YaHei', 9, 'bold'), fg='#636e72', bg=self.colors['bg_light']) self.device_status_label.pack(side=tk.LEFT) # VIN信息 vin_frame = tk.Frame(status_bar_frame, bg=self.colors['bg_light']) vin_frame.pack(side=tk.LEFT, padx=20, pady=5) tk.Label(vin_frame, text="VIN码:", font=('Microsoft YaHei', 9), fg=self.colors['text'], bg=self.colors['bg_light']).pack(side=tk.LEFT) self.vin_label = tk.Label(vin_frame, text="未获取", font=('Microsoft YaHei', 9, 'bold'), fg='#636e72', bg=self.colors['bg_light']) self.vin_label.pack(side=tk.LEFT, padx=(5, 0)) # 授权状态 auth_frame = tk.Frame(status_bar_frame, bg=self.colors['bg_light']) auth_frame.pack(side=tk.LEFT, padx=20, pady=5) tk.Label(auth_frame, text="授权:", font=('Microsoft YaHei', 9), fg=self.colors['text'], bg=self.colors['bg_light']).pack(side=tk.LEFT) self.auth_label = tk.Label(auth_frame, text="未验证", font=('Microsoft YaHei', 9, 'bold'), fg='#636e72', bg=self.colors['bg_light']) self.auth_label.pack(side=tk.LEFT, padx=(5, 0)) # 刷新按钮 refresh_btn = tk.Button(status_bar_frame, text="🔄 检查", command=lambda: self.refresh_device_status(force=True), font=('Microsoft YaHei', 8), fg=self.colors['accent'], bg=self.colors['bg_light'], relief=tk.FLAT, cursor='hand2') refresh_btn.pack(side=tk.RIGHT, padx=10, pady=5) # 解压进度条框架 progress_frame = tk.Frame(main_frame, bg=self.colors['bg_dark']) progress_frame.pack(fill=tk.X, pady=(5, 5)) self.progress_label = tk.Label(progress_frame, text="", font=('Microsoft YaHei', 9), fg=self.colors['text_secondary'], bg=self.colors['bg_dark']) self.progress_label.pack() self.progress = ttk.Progressbar(progress_frame, mode='determinate', style='TProgressbar') self.progress.pack(fill=tk.X, pady=(2, 0)) # 推送进度条 self.push_progress_label = tk.Label(progress_frame, text="", font=('Microsoft YaHei', 9), fg=self.colors['text_secondary'], bg=self.colors['bg_dark']) self.push_progress = ttk.Progressbar(progress_frame, mode='determinate', style='TProgressbar') # 日志区域(下方) log_card = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) log_card.pack(fill=tk.BOTH, expand=True, pady=(5, 0)) # 日志标题栏 log_title_frame = tk.Frame(log_card, bg=self.colors['bg_dark'], height=30) log_title_frame.pack(fill=tk.X) log_title_frame.pack_propagate(False) tk.Label(log_title_frame, text="📋 运行日志", font=('Microsoft YaHei', 10, 'bold'), fg=self.colors['accent'], bg=self.colors['bg_dark']).pack(side=tk.LEFT, padx=10) # 日志文本框 text_frame = tk.Frame(log_card, bg=self.colors['bg_light']) text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.log_text = scrolledtext.ScrolledText(text_frame, height=12, wrap=tk.WORD, font=('Consolas', 9), bg='#2d2d3d', fg='#e0e0e0', insertbackground='white', relief=tk.FLAT, borderwidth=0) self.log_text.pack(fill=tk.BOTH, expand=True) # 配置日志颜色标签 self.log_text.tag_config('INFO', foreground='#74b9ff') self.log_text.tag_config('SUCCESS', foreground='#55efc4') self.log_text.tag_config('ERROR', foreground='#ff7675') self.log_text.tag_config('WARNING', foreground='#ffeaa7') self.log_text.tag_config('CMD', foreground='#a29bfe') # 底部状态栏 bottom_status = tk.Frame(main_frame, bg=self.colors['bg_light'], height=22) bottom_status.pack(fill=tk.X, pady=(5, 0)) bottom_status.pack_propagate(False) self.status_text = tk.Label(bottom_status, text="就绪", font=('Microsoft YaHei', 8), fg=self.colors['text_secondary'], bg=self.colors['bg_light']) self.status_text.pack(side=tk.LEFT, padx=10) # 绑定悬停效果 self.bind_hover_effects() def bind_hover_effects(self): """绑定按钮悬停效果""" buttons = [self.btn_push, self.btn_install_all, self.btn_install_single, self.btn_language, self.btn_timezone, self.btn_settings, self.btn_reboot, self.btn_clear, self.btn_exit] for btn in buttons: original_bg = btn.cget('bg') def on_enter(e, btn=btn, bg=original_bg): btn.config(bg=self.lighten_color(bg)) def on_leave(e, btn=btn, bg=original_bg): btn.config(bg=bg) btn.bind('', on_enter) btn.bind('', on_leave) def center_window(self): """将窗口居中显示在屏幕上""" self.root.update_idletasks() screen_w = self.root.winfo_screenwidth() screen_h = self.root.winfo_screenheight() win_w = self.root.winfo_reqwidth() win_h = self.root.winfo_reqheight() x = (screen_w - win_w) // 2 y = (screen_h - win_h) // 2 self.root.geometry(f"+{x}+{y}") def lighten_color(self, color): """调亮颜色""" if color == self.colors['accent']: return self.colors['accent_hover'] elif color == self.colors['warning']: return '#feca57' elif color == self.colors['info']: return '#0984e3' elif color == self.colors['error']: return '#e17055' elif color == self.colors['success']: return '#00a884' return color def run_on_ui_thread(self, func, *args, **kwargs): """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) def _log_impl(self, message, level="INFO"): """日志写入的实际实现(必须在主线程调用)""" timestamp = datetime.now().strftime("%H:%M:%S") log_entry = f"[{timestamp}] [{level}] {message}\n" self.log_text.insert(tk.END, log_entry, level) self.log_text.see(tk.END) def log(self, message, level="INFO"): """添加日志(线程安全)""" self.run_on_ui_thread(self._log_impl, message, level) def clear_log(self): """清空日志""" self.log_text.delete(1.0, tk.END) self.log("日志已清空", "INFO") def _show_progress_impl(self, show=True, is_push=False): """显示/隐藏进度条的实际实现(必须在主线程调用)""" if is_push: if show: self.push_progress_label.pack() self.push_progress.pack(fill=tk.X, pady=(2, 0)) self.push_progress['value'] = 0 else: self.push_progress_label.pack_forget() self.push_progress.pack_forget() else: if show: self.progress_label.pack() self.progress.pack(fill=tk.X, pady=(2, 0)) self.progress['value'] = 0 else: self.progress_label.pack_forget() self.progress.pack_forget() def show_progress(self, show=True, is_push=False): """显示/隐藏进度条(线程安全)""" self.run_on_ui_thread(self._show_progress_impl, show, is_push) def _update_progress_impl(self, value, max_value=100, label="", is_push=False): """更新进度条的实际实现(必须在主线程调用)""" if is_push: percent = (value / max_value) * 100 self.push_progress['value'] = percent self.push_progress_label.config(text=f"{label}: {value}/{max_value} ({percent:.1f}%)") else: percent = (value / max_value) * 100 self.progress['value'] = percent self.progress_label.config(text=f"{label}: {value}/{max_value} ({percent:.1f}%)") self.root.update_idletasks() def update_progress(self, value, max_value=100, label="", is_push=False): """更新进度条(线程安全)""" self.run_on_ui_thread(self._update_progress_impl, value, max_value, label, is_push) def update_device_status(self, connected, vin=None, authorized=False): """更新设备状态显示(线程安全:立即设状态变量,UI走主线程)""" self.device_connected = connected if vin is not None: self.vin = vin self.run_on_ui_thread(self._update_device_status_impl, connected, vin, authorized) def _update_device_status_impl(self, connected, vin, authorized): """设备状态UI更新的实际实现(必须在主线程调用)""" if connected: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['success']) self.device_status_label.config(text="已连接", fg=self.colors['success']) if vin: self.vin_label.config(text=vin, fg=self.colors['success']) if authorized: self.auth_label.config(text="已授权", fg=self.colors['success']) else: self.auth_label.config(text="未授权", fg=self.colors['error']) else: self.vin_label.config(text="未获取", fg=self.colors['error']) self.auth_label.config(text="未验证", fg=self.colors['error']) else: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['error']) self.device_status_label.config(text="未连接", fg=self.colors['error']) self.vin_label.config(text="未获取", fg=self.colors['error']) self.auth_label.config(text="未验证", fg=self.colors['error']) def check_device_connection(self): """检查设备是否连接""" if not self.device_connected: messagebox.showwarning("设备未连接", "请先连接设备并点击「检查」按钮刷新状态!") return False return True def start_device_monitor(self): """启动设备状态监控(每5秒检查一次)""" def monitor(): while True: try: result = subprocess.run('adb -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().split('\n') devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] if devices and not self.device_connected and not self._refreshing: # 设备新连接,刷新状态 self.refresh_device_status() elif not devices and self.device_connected: # 设备断开连接 self.update_device_status(False) self.log("设备已断开连接", "WARNING") time.sleep(5) except: time.sleep(5) threading.Thread(target=monitor, daemon=True).start() # ============================================================ # 核心:adb shell 自动密码输入 # ============================================================ def run_adb_shell(self, shell_command): """执行 adb shell 命令,自动静默输入设备密码 adb36987。 静默执行,不显示 adb 原始输出,仅返回结果。""" try: proc = subprocess.Popen( f'adb -d shell {shell_command}', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) stdout, stderr = proc.communicate(input='adb36987\n', timeout=15) # 过滤密码提示行 output_lines = [] for line in stdout.split('\n'): stripped = line.strip() if 'please input verify password' in stripped.lower(): continue if stripped == 'verify success!': continue output_lines.append(line) output = '\n'.join(output_lines).strip() if proc.returncode == 0: return True, output else: return False, stderr.strip() except subprocess.TimeoutExpired: proc.kill() proc.communicate() return False, "命令超时" except Exception as e: return False, str(e) # ============================================================ # 原始 adb 命令(用于 adb push / adb install 等不需要 shell 的操作) # ============================================================ def run_adb_command(self, command): """执行原始 adb 命令(adb push / adb install 等,无需 shell 密码)。 静默执行,不显示 adb 原始输出,仅返回结果。""" try: result = subprocess.run(command, shell=True, capture_output=True, text=True, encoding='utf-8') if result.returncode == 0: return True, result.stdout.strip() else: return False, result.stderr.strip() except Exception as e: return False, str(e) def check_package_extracted(self): """检查语言包是否已解压""" has_app = self.apps_dir and self.apps_dir.exists() and len(list(self.apps_dir.glob("*.apk"))) > 0 return has_app def extract_package_silent(self): """静默解压语言包(带进度)—— 逸动版仅处理 app 目录""" if not self.package_file.exists(): self.log(f"错误:未找到资源包", "ERROR") return False try: # 使用用户目录,无需管理员权限 local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path.mkdir(parents=True, exist_ok=True) self.temp_dir = hidden_path / "apps_cache_yidong" # 如果已存在,先清理 if self.temp_dir.exists(): shutil.rmtree(self.temp_dir, ignore_errors=True) time.sleep(0.5) self.temp_dir.mkdir(parents=True, exist_ok=True) # 设置隐藏属性(Windows) if sys.platform == 'win32': subprocess.run(f'attrib +h "{self.temp_dir.parent}"', shell=True, capture_output=True) subprocess.run(f'attrib +h "{self.temp_dir}"', shell=True, capture_output=True) self.log(f"正在准备资源,耗时较长,请耐心等待...", "INFO") # 使用 7za 高速解压 self.update_progress(0, 1, "资源加载中...") result = subprocess.run( [self.sz, 'x', str(self.package_file), f'-p{self.extract_password}', f'-o{self.temp_dir}', '-y'], capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 ) if result.returncode != 0: stderr = result.stderr.strip() if stderr: self.log(f"解压失败: {stderr[:200]}", "ERROR") else: self.log("解压失败,请检查密码是否正确", "ERROR") return False self.update_progress(1, 1, "资源加载完成") # 查找 app 目录(逸动无 priv-app) self.apps_dir = None app_candidates = list(self.temp_dir.rglob("apps")) if app_candidates: self.apps_dir = app_candidates[0] if not self.apps_dir: self.log("警告:未找到 apps 目录", "WARNING") return False return True except Exception as e: self.log(f"资源准备失败", "ERROR") return False def check_environment(self): """检查环境""" try: result = subprocess.run('adb version', shell=True, capture_output=True, text=True) if result.returncode == 0: self.refresh_device_status() if not self.package_file.exists(): self.log("未找到资源包文件", "WARNING") else: self.log("未找到adb命令,请将ADB文件放入本目录", "ERROR") except FileNotFoundError: self.log("未找到adb命令,请将ADB文件放入本目录", "ERROR") def refresh_device_status(self, force=False): """刷新设备状态 —— 逸动版使用 ca.car.vin 获取 VIN""" # 防止并发刷新(手动点击「检查」时强制忽略锁) if self._refreshing and not force: return self._refreshing = True def refresh(): was_connected = self.device_connected # 检查设备连接 result = subprocess.run('adb -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().split('\n') devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] if devices: if not was_connected: self.log("设备已连接", "SUCCESS") # 获取VIN —— 逸动车型使用 ca.car.vin success, vin_output = self.run_adb_shell( 'settings get system ca.car.vin') vin = vin_output.strip() if success else '' if vin: self.log(f"VIN: {vin}", "INFO") authorized = self.check_authorization(vin) self.update_device_status(True, vin, authorized) else: self.log("无法获取VIN,请确认设备已进入工厂模式", "WARNING") self.update_device_status(True, None, False) else: if was_connected: self.log("设备未连接", "WARNING") self.update_device_status(False) self._refreshing = False threading.Thread(target=refresh, daemon=True).start() def check_authorization(self, vin): """检查授权""" self.log("正在验证授权状态...", "INFO") try: url = f"{self.api_url}?vin={vin}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: data = json.loads(response.read().decode('utf-8')) if data.get('authorized') == True: self.log("✅ 授权验证通过!", "SUCCESS") if 'data' in data and 'vehicleName' in data['data']: self.log(f"车辆名称: {data['data']['vehicleName']}", "INFO") return True else: self.log(f"❌ 授权验证失败", "ERROR") return False except Exception as e: self.log(f"❌ 授权验证失败", "ERROR") return False def fetch_package_password(self): """从服务端获取资源包解压密码""" if not self.vin: self.log("请先连接adb!", "ERROR") return False try: pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" url = f"{pwd_api_url}?vin={self.vin}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: data = json.loads(response.read().decode('utf-8')) if data.get('success') and 'data' in data and 'password' in data['data']: self.extract_password = data['data']['password'] return True else: self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") return False except Exception as e: self.log(f"数据准备失败: {str(e)}", "ERROR") return False def push_single_apk(self, apk_path, apk_name): """推送单个APK到设备并安装,返回 (成功, 错误信息)""" temp_apk_path = f"/data/local/tmp/{apk_name}.apk" ok, err = self.run_adb_command(f'adb -d push "{apk_path}" {temp_apk_path}') if not ok: return False, f"push失败: {err}" ok, err = self.run_adb_shell(f'pm install -r {temp_apk_path}') self.run_adb_shell(f'rm -f {temp_apk_path}') if not ok: return False, f"install失败: {err}" return True, "" def _push_and_install(self, apk_path, apk_name): """push → pm install → cleanup,供安装类方法复用""" ok, _ = self.push_single_apk(apk_path, apk_name) return ok def push_all_apks(self): """推送APK并安装 —— 逸动版仅处理 app 目录,使用 pm install""" # 检查设备连接(仅 UI 层检查在主线程,其余工作进后台线程) if not self.check_device_connection(): return if not self.vin: messagebox.showwarning("警告", "请先刷新设备状态并获取VIN码") return def do_push_all(): # 验证授权 if not self.check_authorization(self.vin): self.run_on_ui_thread(lambda: messagebox.showerror("授权失败", "设备未授权")) return # 获取解压密码 if not self.extract_password: if not self.fetch_package_password(): self.run_on_ui_thread(lambda: messagebox.showerror("错误", "数据准备失败!")) return # 解压 if not self.check_package_extracted(): self.log("正在准备资源...", "INFO") self.show_progress(True, is_push=False) if not self.extract_package_silent(): self.show_progress(False, is_push=False) self.run_on_ui_thread(lambda: messagebox.showerror("错误", "资源准备失败!")) return self.show_progress(False, is_push=False) if not self.apps_dir or not self.apps_dir.exists(): self.run_on_ui_thread(lambda: messagebox.showerror("错误", "资源目录未找到")) return # 开始刷入 self.show_progress(True, is_push=True) self.log("开始刷入语言包...", "INFO") self.run_adb_shell('mkdir -p /data/local/tmp') self.run_adb_shell('setprop vecentek.model 1') all_apks = list(self.apps_dir.glob("*.apk")) if not all_apks: self.log("未找到语言包文件", "WARNING") self.show_progress(False, is_push=True) return total = len(all_apks) success_count = 0 for i, apk_path in enumerate(all_apks, 1): apk_name = apk_path.stem ok, _ = self.push_single_apk(apk_path, apk_name) if ok: self.log(f"安装成功: {apk_name}.apk", "SUCCESS") success_count += 1 else: self.log(f"安装失败: {apk_name}.apk", "ERROR") self.update_progress(i, total, "正在刷入...", is_push=True) self.update_progress(total, total, "刷入完成", is_push=True) self.run_adb_shell('setprop vecentek.model 0') if success_count > 0: self._enable_overlays() else: self.log("语言包刷入失败", "ERROR") self.show_progress(False, is_push=True) threading.Thread(target=do_push_all, daemon=True).start() def _enable_overlays(self): """启用 overlay 包""" overlays = [ "com.android.systemui.overlay", "com.incall.apps.airconditioner.overlay", "com.incall.apps.launcher.overlay", "com.incall.dvr.overlay", ] # self.log("正在启用 overlay...", "INFO") for pkg in overlays: success, _ = self.run_adb_shell( f'cmd overlay enable --user current {pkg}') def install_all_apks(self): """批量安装APK — push → pm install → cleanup""" if not self.check_device_connection(): return if not self.vin: messagebox.showwarning("警告", "请先刷新设备状态并获取VIN码") return if not self.check_authorization(self.vin): messagebox.showerror("授权失败", "设备未授权,无法执行此操作") return # 使用当前目录下的apks文件夹 apk_dir = self.base_dir / "apks" # 检查apk文件夹是否存在 if not apk_dir.exists(): messagebox.showerror("错误", "未找到apks文件夹!\n请在程序目录下创建apks文件夹并放入APK文件。") self.log("未找到apks文件夹", "ERROR") return # 查找所有apk文件 apk_files = list(apk_dir.glob("*.apk")) if not apk_files: messagebox.showerror("错误", "apks文件夹中没有找到APK文件!") self.log("apk文件夹中没有找到APK文件", "ERROR") return # 询问是否确认安装 result = messagebox.askyesno("确认安装", f"找到 {len(apk_files)} 个APK文件\n\n是否开始批量安装?") if not result: return def install(): self.show_progress(True, is_push=True) total = len(apk_files) self.log(f"开始批量安装 {total} 个APK...", "INFO") self.run_adb_shell('setprop vecentek.model 1') success_count = 0 for i, apk_path in enumerate(apk_files, 1): apk_name = apk_path.stem self.update_progress(i, total, "安装中...", is_push=True) if self._push_and_install(apk_path, apk_name): self.log(f"安装成功: {apk_name}.apk", "SUCCESS") success_count += 1 else: self.log(f"安装失败: {apk_name}.apk", "ERROR") self.run_adb_shell('setprop vecentek.model 0') self.update_progress(total, total, "安装完成", is_push=True) self.show_progress(False, is_push=True) if success_count == total: messagebox.showinfo("安装完成", f"成功安装 {total} 个APK!") elif success_count > 0: messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {total - success_count}") messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {total - success_count}") else: self.log("安装失败", "ERROR") messagebox.showerror("安装失败", "所有APK安装失败!") threading.Thread(target=install, daemon=True).start() def install_single_apk(self): """安装单个APK — push → pm install → cleanup""" if not self.check_device_connection(): return if not self.vin: messagebox.showwarning("警告", "请先刷新设备状态并获取VIN码") return if not self.check_authorization(self.vin): messagebox.showerror("授权失败", "设备未授权,无法执行此操作") return file_path = filedialog.askopenfilename( title="选择APK文件", filetypes=[("APK文件", "*.apk"), ("所有文件", "*.*")] ) if not file_path: return def install(): apk_name = Path(file_path).stem self.show_progress(True, is_push=True) self.update_progress(30, 100, "安装中", is_push=True) self.run_adb_shell('setprop vecentek.model 1') success = self._push_and_install(file_path, apk_name) self.run_adb_shell('setprop vecentek.model 0') self.update_progress(100, 100, "完成", is_push=True) if success: self.log("安装成功", "SUCCESS") else: self.log("安装失败", "ERROR") self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() def open_language_settings(self): """打开系统语言设置""" if not self.check_device_connection(): return self.run_adb_shell('am start -a android.settings.LOCALE_SETTINGS') def open_language_quick_set(self): """打开快捷语言设置弹窗""" # 检查设备连接 if not self.check_device_connection(): return # 创建弹窗 popup = tk.Toplevel(self.root) popup.title("快捷语言设置") popup.geometry("520x320") popup.configure(bg=self.colors['bg_dark']) popup.resizable(False, False) # 居中显示 popup.update_idletasks() x = self.root.winfo_x() + (self.root.winfo_width() - 520) // 2 y = self.root.winfo_y() + (self.root.winfo_height() - 320) // 2 popup.geometry(f"+{x}+{y}") popup.transient(self.root) popup.grab_set() # 标题 header = tk.Label(popup, text="选择目标语言", font=('Microsoft YaHei', 13, 'bold'), fg=self.colors['accent'], bg=self.colors['bg_dark']) header.pack(pady=(15, 10)) hint = tk.Label(popup, text="点击按钮即可将系统语言切换为对应语言,重启后生效", font=('Microsoft YaHei', 9), fg=self.colors['text_secondary'], bg=self.colors['bg_dark']) hint.pack(pady=(0, 12)) # 语言列表:(显示名, locale_code) languages = [ ("🇨🇳 中文", "zh-CN"), ("英 English", "en-US"), ("俄 Русский", "ru-RU"), ("法 Français", "fr-FR"), ("西 Español", "es-ES"), ("葡 Português", "pt-BR"), ("意 Italiano", "it-IT"), ("阿 العربية", "ar-SA"), ] # 创建按钮容器 btn_frame = tk.Frame(popup, bg=self.colors['bg_dark']) btn_frame.pack(pady=(0, 10)) btn_colors = [ self.colors['accent'], self.colors['info'], self.colors['success'], self.colors['warning'], '#e17055', '#00b894', '#6c5ce7', '#0984e3', ] for i, (label, locale) in enumerate(languages): row = i // 4 col = i % 4 def make_cmd(loc=locale, lbl=label): return lambda: self._quick_set_language(loc, lbl, popup) btn = tk.Button(btn_frame, text=label, command=make_cmd(), font=('Microsoft YaHei', 10), fg='white', bg=btn_colors[i], relief=tk.FLAT, cursor='hand2', width=12, height=2) btn.grid(row=row, column=col, padx=5, pady=5) # 底部分隔 + 打开系统设置入口 sep = tk.Frame(popup, bg=self.colors['border'], height=1) sep.pack(fill=tk.X, padx=20, pady=(8, 6)) sys_btn = tk.Button(popup, text="⚙️ 打开系统语言设置(手动选择)", command=lambda: self._open_sys_and_close(popup), font=('Microsoft YaHei', 9), fg=self.colors['text_secondary'], bg=self.colors['bg_light'], relief=tk.FLAT, cursor='hand2') sys_btn.pack(pady=(0, 10)) def _quick_set_language(self, locale_code, language_name, popup): """执行快捷语言设置""" popup.destroy() def do_set(): self.log(f"正在设置系统语言为: {language_name} ({locale_code})", "INFO") success, output = self.run_adb_shell( f'settings put system system_locales {locale_code}' ) if success: self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") messagebox.showinfo( "设置成功", f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" ) else: self.log(f"✗ 语言设置失败: {output}", "ERROR") messagebox.showerror("设置失败", f"语言设置失败!\n\n{output}") threading.Thread(target=do_set, daemon=True).start() def _open_sys_and_close(self, popup): """关闭弹窗并打开系统语言设置""" popup.destroy() self.open_language_settings() def open_timezone_settings(self): """打开时区设置""" if not self.check_device_connection(): return self.run_adb_shell('am start -a android.settings.TIMEZONE_SETTINGS') def open_android_settings(self): """打开安卓原生设置""" if not self.check_device_connection(): return self.run_adb_shell('am start -a android.settings.SETTINGS') def reboot_device(self): """重启设备""" if not self.check_device_connection(): return if messagebox.askyesno("确认重启", "确定要重启设备吗?"): proc = subprocess.Popen(f'{self.adb} -d shell reboot', shell=True, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) try: proc.stdin.write(b'adb36987\n') proc.stdin.flush() proc.stdin.close() except: pass self.log("设备正在重启...", "INFO") self.update_device_status(False) def on_disable_upgrade(self): """禁用系统升级""" # 检查设备连接 if not self.check_device_connection(): return # 弹窗确认 result = messagebox.askyesno( "确认禁用升级", "⚠️ 警告:禁用系统升级后,将无法接收系统更新!\n\n" "是否确定要禁用系统升级应用?\n\n" "禁用命令:\n" "adb shell pm disable-user --user 0 com.incall.apps.softmanager" ) if not result: self.log("已取消禁用升级操作", "INFO") return def disable(): self.show_progress(True, is_push=False) success, output = self.run_adb_shell( 'pm disable-user --user 0 com.incall.apps.softmanager') if success: self.log("系统升级已禁用", "SUCCESS") messagebox.showinfo("成功", "系统升级已成功禁用!") else: self.log("禁用系统升级失败", "ERROR") messagebox.showerror("错误", f"禁用失败:{output}") self.show_progress(False, is_push=False) threading.Thread(target=disable, daemon=True).start() def run(self): """运行程序""" self.root.mainloop() def main(): """主函数""" if sys.version_info < (3, 6): print("错误:需要Python 3.6或更高版本") sys.exit(1) try: app = ADKAPKGUI() app.run() except Exception as e: print(f"启动失败: {e}") import traceback traceback.print_exc() messagebox.showerror("错误", f"程序启动失败: {e}") if __name__ == "__main__": main()