{"repo": "bilieebiliee1-design/ai-text-assistant-plugin", "files": {"src/w.java": {"4afbcd00682fe2c130cbe5e9ee0200bdb27bc0dc": {"t": 1785746110.6581802, "n": 109339, "h": "5bec6f108868b13d", "c": "package impl;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.ColorStateList;\nimport android.graphics.*;\nimport android.graphics.drawable.*;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.view.*;\nimport android.widget.*;\nimport android.webkit.*;\nimport bin.mt.plugin.api.PluginContext;\nimport bin.mt.plugin.api.preference.PluginPreference;\nimport bin.mt.plugin.api.ui.*;\nimport bin.mt.plugin.api.ui.builder.PluginRootLayoutBuilder;\n\nimport android.util.Base64;\nimport java.io.ByteArrayOutputStream;\nimport java.io.InputStream;\nimport java.io.IOException;\n\nimport org.json.JSONObject;\nimport java.lang.ref.WeakReference;\nimport java.io.*;\nimport java.nio.file.*;\nimport java.util.*;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.*;\n\nimport android.app.DownloadManager;\nimport android.content.Context;\nimport android.net.Uri;\nimport android.os.Environment;\nimport android.widget.Toast;\n\nimport android.os.StatFs;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.BatteryManager;\nimport android.hardware.SensorManager;\nimport android.hardware.Sensor;\nimport android.telephony.TelephonyManager;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.net.NetworkInterface;\nimport java.net.InetAddress;\nimport java.net.Inet4Address;\nimport java.util.Enumeration;\n\n\nimport java.util.Map;\n\nimport android.animation.ValueAnimator;\nimport android.animation.TimeInterpolator;\nimport android.view.animation.LinearInterpolator;\nimport android.graphics.Shader;\nimport android.graphics.LinearGradient;\nimport android.graphics.Matrix;\n\nimport android.view.DragEvent;\nimport java.util.Collections;\nimport java.util.HashMap;\n\npublic class w implements PluginPreference {\n\n PluginContext c;\n O o;\n public static WeakReference weakContext;\n\n // 预定义的 API 服务商列表 (保持不变)\n static java.util.List

P = new ArrayList<>();\n\n static void initProviders() {\n if (!P.isEmpty()) return;\n P.addAll(Arrays.asList(\n new p(\"SiliconFlow\", \"https://api.siliconflow.cn/v1/chat/completions\", \"https://cloud.siliconflow.cn/i/3dPeL6IV\", new String[]{\"deepseek-ai/DeepSeek-V3\", \"Qwen/Qwen2.5-Coder-32B-Instruct\", \"Pro/deepseek-ai/DeepSeek-R1\", \"meta-llama/Llama-4-Maverick-17B-128E-Instruct\"}),\n new p(\"Groq\", \"https://api.groq.com/openai/v1/chat/completions\", \"https://console.groq.com/keys\", new String[]{\"llama-3.3-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\", \"gemma2-9b-it\"}),\n new p(\"OpenRouter\", \"https://openrouter.ai/api/v1/chat/completions\", \"https://openrouter.ai/keys\", new String[]{\"openai/gpt-oss-120b\", \"meta-llama/llama-4-maverick-17b-128e-instruct\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-flash-1.5\"}),\n new p(\"Together AI\", \"https://api.together.xyz/v1/chat/completions\", \"https://api.together.ai/settings/api-keys\", new String[]{\"meta-llama/Llama-4-Maverick-17B-128E-Instruct\", \"deepseek-ai/DeepSeek-R1\", \"Qwen/Qwen3-32B\", \"mistralai/Mixtral-8x22B-Instruct\"}),\n new p(\"Fireworks AI\", \"https://api.fireworks.ai/inference/v1/chat/completions\", \"https://fireworks.ai/api-keys\", new String[]{\"accounts/fireworks/models/llama-4-maverick-17b-128e-instruct\", \"accounts/fireworks/models/deepseek-r1\", \"accounts/fireworks/models/qwen3-32b\"}),\n new p(\"Cloudflare Workers AI\", \"https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/chat/completions\", \"https://dash.cloudflare.com/?to=/:account/workers-ai\", new String[]{\"@cf/meta/llama-4-maverick-17b-128e-instruct\", \"@cf/qwen/qwen3-32b\", \"@cf/openai/gpt-oss-120b\"}),\n new p(\"Hugging Face\", \"https://router.huggingface.co/v1/chat/completions\", \"https://huggingface.co/settings/tokens\", new String[]{\"meta-llama/Llama-4-Maverick-17B-128E-Instruct\", \"Qwen/Qwen3-32B\", \"google/gemma-3-27b-it\"}),\n new p(\"讯飞星火\", \"https://spark-api-open.xf-yun.com/v1/chat/completions\", \"https://console.xfyun.cn/services/bm35\", new String[]{\"generalv4\", \"4.0Ultra\", \"generalv3.5\"}),\n new p(\"百度千帆\", \"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions\", \"https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application\", new String[]{\"ernie-4.5-8k\", \"ernie-4.5-turbo\", \"ernie-speed-128k\"}),\n new p(\"腾讯混元\", \"https://api.hunyuan.cloud.tencent.com/v1/chat/completions\", \"https://console.cloud.tencent.com/hunyuan/apikey\", new String[]{\"hunyuan-turbo\", \"hunyuan-pro\", \"hunyuan-large\"}),\n new p(\"智谱AI\", \"https://open.bigmodel.cn/api/paas/v4/chat/completions\", \"https://www.bigmodel.cn/invite?icode=ImA%2FFxt5NUUX36RyLLBwAEjPr3uHog9F4g5tjuOUqno%3D\", new String[]{\"glm-4-9b-chat\", \"glm-4v-plus\", \"glm-4-flash\"}),\n new p(\"阿里通义千问\", \"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions\", \"https://dashscope.console.aliyun.com/apiKey\", new String[]{\"qwen-max\", \"qwen-plus\", \"qwen-turbo\", \"qwen3-32b\"}),\n new p(\"Moonshot\", \"https://api.moonshot.cn/v1/chat/completions\", \"https://platform.moonshot.cn/console/api-keys\", new String[]{\"moonshot-v1-128k\", \"moonshot-v1-32k\", \"moonshot-v1-8k\"}),\n new p(\"ChatAnywhere\", \"https://api.chatanywhere.org/v1/chat/completions\", \"https://github.com/chatanywhere/GPT_API_free\", new String[]{\"gpt-4o-mini\", \"gpt-3.5-turbo\", \"deepseek-chat\"}),\n new p(\"Mistral AI\", \"https://api.mistral.ai/v1/chat/completions\", \"https://console.mistral.ai/api-keys\", new String[]{\"mistral-large-latest\", \"mistral-medium-3\", \"open-mistral-nemo\", \"pixtral-12b\"}),\n new p(\"Cohere\", \"https://api.cohere.ai/v1/chat/completions\", \"https://dashboard.cohere.com/api-keys\", new String[]{\"command-r-plus\", \"command-a-03-2025\", \"command-r\"}),\n new p(\"Perplexity\", \"https://api.perplexity.ai/chat/completions\", \"https://www.perplexity.ai/settings/api\", new String[]{\"sonar-pro\", \"sonar-reasoning-pro\", \"sonar-deep-research\", \"r1-1776\"}),\n new p(\"DeepInfra\", \"https://api.deepinfra.com/v1/openai/chat/completions\", \"https://deepinfra.com/dashboard/api_keys\", new String[]{\"meta-llama/Llama-4-Maverick-17B-128E-Instruct\", \"deepseek-ai/DeepSeek-R1\", \"Qwen/Qwen3-32B\"}),\n new p(\"Novita AI\", \"https://api.novita.ai/v1/chat/completions\", \"https://novita.ai/dashboard/api-keys\", new String[]{\"deepseek/deepseek-r1-turbo\", \"meta-llama/Llama-4-Maverick-17B\", \"Qwen/Qwen3-32B\"}),\n new p(\"DeepSeek\", \"https://api.deepseek.com/v1/chat/completions\", \"https://platform.deepseek.com/api_keys\", new String[]{\"deepseek-chat\", \"deepseek-reasoner\", \"deepseek-v3.2\"}),\n new p(\"OpenAI\", \"https://api.openai.com/v1/chat/completions\", \"https://platform.openai.com/api-keys\", new String[]{\"gpt-5.2\", \"gpt-4o\", \"gpt-4o-mini\", \"o3-pro\"}),\n new p(\"Grok (xAI)\", \"https://api.x.ai/v1/chat/completions\", \"https://console.x.ai/api-keys\", new String[]{\"grok-4\", \"grok-4.1-fast\", \"grok-3\"}),\n new p(\"Gemini (Google)\", \"https://generativelanguage.googleapis.com/v1beta/openai/chat/completions\", \"https://ai.google.dev/gemini-api/docs/api-key\", new String[]{\"gemini-2.5-pro\", \"gemini-2.0-flash\", \"gemini-3-pro\"}),\n new p(\"Anthropic (Claude)\", \"https://api.anthropic.com/v1/chat/completions\", \"https://console.anthropic.com/settings/keys\", new String[]{\"claude-opus-4-1\", \"claude-sonnet-4-5\", \"claude-haiku-4-5\"}),\n new p(\"自定义提供商\", \"\", \"\", new String[]{})\n ));\n }\n\n static class p {\n String n, u, k; String[] m;\n p(String n, String u, String k, String[] m) { this.n = n; this.u = u; this.k = k; this.m = m; }\n }\n\n public void init(@NonNull PluginContext c) {\n this.c = c;\n weakContext = new WeakReference<>(c);\n this.o = new O();\n this.o.init(c);\n }\n\n @Override\n public void onBuild(@NonNull PluginContext c, @NonNull Builder b) {\n this.c = c;\n weakContext = new WeakReference<>(c);\n if (o == null) { o = new O(); o.init(c); }\n initProviders();\n SharedPreferences s = c.getPreferences();\n\n String uTemp = O.gUrl(c);\n if (uTemp.isEmpty()) uTemp = P.get(0).u;\n final String u = uTemp;\n\n String m = s.getString(O._0, P.get(0).m[0]);\n String n = g(u);\n boolean k = !O.g(c).isEmpty();\n\n b.title(\"AI文本分析助手设置\");\n\n PluginPreference.Text def = b.addText(\"API 服务配置\").summary(\"当前: \" + n + \" / \" + m);\n def.onClick((i, t) -> S(i, s, t, def));\n\n boolean isDg = O.isDowngraded(c);\n String apiKeySummary = k ? (isDg ? \"已存储 (⚠️安全降级模式)\" : \"已加密存储 (点击修改)\") : \"未设置 (点击设置)\";\n PluginPreference.Text apiKeyPref = b.addText(\"API 密钥\");\n apiKeyPref.summary(apiKeySummary);\n apiKeyPref.onClick((i, t) -> K(i, z -> {\n apiKeyPref.summary(z);\n u(t, z);\n if (t != apiKeyPref) u(apiKeyPref, z);\n }));\n\n b.addText(\"密钥保管箱\").summary(\"管理多组API密钥\").onClick((i, t) -> { if (o != null) o.kv(i); });\n b.addText(\"获取 API Key\").summary(\"点击跳转当前服务商官网\").onClick((i, t) -> U(i, u));\n\n b.addHeader(\"参数与提示词\");\n b.addText(\"模型参数\").summary(\"Temperature, Max Tokens 等\").onClick((i, t) -> P(i));\n b.addText(\"分析提示词\").summary(\"点击编辑分析使用的 Prompt\").onClick((i, t) -> p(i));\n\n b.addHeader(\"界面定制\");\n b.addText(\"背景与颜色设置\").summary(\"透明度、自定义背景API、颜色定制\").onClick((i, t) -> B(i));\n\n b.addHeader(\"功能管理\");\n b.addSwitch(\"自动保存密钥到保管箱\", \"api_auto_kv\").defaultValue(false).summary(\"开启后,配置或使用的 API Key 将自动保存到密钥保管箱\");\n b.addSwitch(\"全局系统悬浮窗\", \"global_float_window\").defaultValue(false).summary(\"开启后,在桌面显示悬浮图标,点击即可对话\");\n \n if (Build.VERSION.SDK_INT >= 23) {\n Context gCtx = getGlobalContext();\n if (gCtx != null && !android.provider.Settings.canDrawOverlays(gCtx)) {\n b.addText(\"⚠️ 授予悬浮窗权限\").summary(\"点击授权以使用全局悬浮窗\").onClick((ui, v) -> {\n Context ctx = getGlobalContext();\n if (ctx != null) {\n Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(\"package:\" + ctx.getPackageName()));\n intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n ctx.startActivity(intent);\n }\n });\n }\n }\n b.addSwitch(\"继续对话时显示历史列表\", O.I).defaultValue(false).summary(\"开启后,点击继续对话将弹出历史记录可供选择,否则默认使用最新对话\");\n b.addSwitch(\"长按拖拽对话框按钮调整顺序\", \"dialog_drag_enabled\").defaultValue(false).summary(\"开启后,对话框的“确定/取消”等按钮支持长按拖拽改变左右顺序\");\n\n b.addText(\"对话管理\").summary(\"查看和管理AI对话历史记录\").onClick((i, t) -> {\n if (o != null) o.h(i);\n });\n b.addText(\"日志查看\").summary(\"查看插件运行日志\").onClick((i, t) -> c.openLogViewer());\n\n b.addHeader(\"关于\");\n b.addText(\"使用指南\").summary(\"查看详细使用说明\").onClick((i, t) -> u(i));\n b.addText(\"关于插件\").summary(\"版本、协议与鸣谢\").onClick((i, t) -> a(i));\n b.addText(\"兼容性智能检测\").summary(\"点击让 AI 分析当前环境是否适合运行本插件\").onClick((i, t) -> C(i));\n\n applyLiquidGlassTheme(c);\n }\n\n // =========================================================\n // 液态玻璃主题重构逻辑 (Liquid Glass V3 - Reflection Based)\n // =========================================================\n\n private void applyLiquidGlassTheme(PluginContext c) {\n new Handler(Looper.getMainLooper()).postDelayed(() -> {\n try {\n Activity activity = findActivity();\n if (activity == null) return;\n\n SharedPreferences s = c.getPreferences();\n int bgMode = s.getInt(\"bg_mode\", 0); // 0:内置, 1:文件, 2:API, 3:本地库\n String customPath = s.getString(\"bg_local_path\", \"\"); // 修正 key\n String apiUrl = s.getString(\"bg_api_url\", \"https://yingciyuan.cn/pe.php\");\n int userAlpha = s.getInt(\"bg_alpha\", 0); // 用户输入的 255\n int alpha = 255 - userAlpha; // 255 - 255 = 0 (全透明);255 - 0 = 255 (不透明)\n\n Window window = activity.getWindow();\n window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);\n window.setStatusBarColor(Color.TRANSPARENT);\n window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);\n\n boolean success = false;\n\n // 背景图加载策略\n try {\n if (bgMode == 0) {\n success = loadBmpFromAsset(c, alpha, window);\n } else if (bgMode == 1) {\n if (!customPath.isEmpty() && new File(customPath).exists()) {\n success = loadBmpFromFile(customPath, alpha, window);\n }\n } else if (bgMode == 2) {\n success = loadBmpFromApiCached(c, apiUrl, alpha, window);\n } else if (bgMode == 3) {\n success = loadBmpFromLocalLibrary(alpha, window);\n }\n } catch (Exception e) {\n success = false;\n new O.L(c).e(\"UI\", \"Bmp load error\", e);\n }\n\n // 失败回退逻辑\n if (!success && bgMode != 0) {\n loadBmpFromAsset(c, alpha, window);\n c.showToast(\"背景加载失败,已回退至默认背景\");\n } else if (!success) {\n // mode 0 失败,尝试重新加载 asset\n loadBmpFromAsset(c, alpha, window);\n }\n\n View rootView = window.getDecorView().findViewById(android.R.id.content);\n processViewsLiquidGlass(rootView);\n schedulePeriodicUpdate(rootView);\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"Theme apply failed\", e);\n }\n }, 10);\n }\n\n private boolean loadBmpFromAsset(PluginContext c, int alpha, Window w) {\n try (java.io.InputStream is = c.getAssetsAsStream(\"B.avif\")) {\n Bitmap bmp = BitmapFactory.decodeStream(is);\n if (bmp != null) {\n w.getDecorView().setBackground(new AspectDrawable(bmp, 0xFFF0F0F0, alpha));\n return true;\n } else {\n // AVIF decoding fallback using WebView\n decodeAvif(c, \"B.avif\", (b) -> {\n if (b != null) {\n w.getDecorView().setBackground(new AspectDrawable(b, 0xFFF0F0F0, alpha));\n }\n });\n return true; // Handled async\n }\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"Asset load failed\", e);\n }\n return false;\n }\n\n private void decodeAvif(PluginContext c, String assetName, ValueCallback cb) {\n // 1. 在子线程进行 IO 操作(读取流并转 Base64),避免阻塞主线程\n new Thread(() -> {\n String base64Image = null;\n try (java.io.InputStream is = c.getAssetsAsStream(assetName)) {\n if (is != null) {\n base64Image = inputStreamToBase64(is);\n }\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"Avif decode failed\", e);\n }\n\n final String finalBase64 = base64Image;\n\n // 2. 切回主线程操作 WebView\n new Handler(Looper.getMainLooper()).post(() -> {\n if (finalBase64 == null) {\n cb.onReceiveValue(null);\n return;\n }\n\n try {\n Context ctx = (c instanceof Context) ? (Context) c : getGlobalContext();\n if (ctx == null) {\n cb.onReceiveValue(null);\n return;\n }\n\n WebView webView = new WebView(ctx);\n // 必须允许内容访问,虽然 Data URI 不需要文件权限,但保持设置无害\n webView.getSettings().setAllowContentAccess(true); \n \n webView.setWebViewClient(new WebViewClient() {\n @Override\n public void onPageFinished(WebView view, String url) {\n // 延迟一会以确保渲染完成\n new Handler().postDelayed(() -> {\n try {\n if (view.getWidth() <= 0 || view.getHeight() <= 0) {\n android.util.DisplayMetrics dm = ctx.getResources().getDisplayMetrics();\n view.measure(View.MeasureSpec.makeMeasureSpec(dm.widthPixels, View.MeasureSpec.EXACTLY),\n View.MeasureSpec.makeMeasureSpec(dm.heightPixels, View.MeasureSpec.EXACTLY));\n view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());\n }\n \n Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);\n Canvas canvas = new Canvas(bitmap);\n view.draw(canvas);\n cb.onReceiveValue(bitmap);\n \n // 建议在回调后清理 WebView,防止内存泄漏,但要确保 draw 已完成\n view.destroy(); \n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"WebView draw failed\", e);\n cb.onReceiveValue(null);\n try { view.destroy(); } catch (Exception ignored) {}\n }\n }, 100); // 100ms 延时,视图片大小可能需要调整\n }\n });\n\n // 初始化布局参数,防止宽高为0\n android.util.DisplayMetrics dm = ctx.getResources().getDisplayMetrics();\n webView.measure(View.MeasureSpec.makeMeasureSpec(dm.widthPixels, View.MeasureSpec.EXACTLY),\n View.MeasureSpec.makeMeasureSpec(dm.heightPixels, View.MeasureSpec.EXACTLY));\n webView.layout(0, 0, webView.getMeasuredWidth(), webView.getMeasuredHeight());\n\n // 3. 构建包含 Base64 图片数据的 HTML\n // 使用 data:image/avif;base64, 协议\n String imgData = \"data:image/avif;base64,\" + finalBase64;\n \n // CSS background 方式,确保覆盖全屏\n String html = \"\" + // 或者 cover,视需求而定\n \"\";\n\n webView.loadDataWithBaseURL(null, html, \"text/html\", \"UTF-8\", null);\n\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"WebView error\", e);\n cb.onReceiveValue(null);\n }\n });\n }).start();\n}\n\n/**\n * 辅助方法:将 InputStream 转换为 Base64 字符串\n */\nprivate String inputStreamToBase64(InputStream is) throws IOException {\n ByteArrayOutputStream buffer = new ByteArrayOutputStream();\n int nRead;\n byte[] data = new byte[16384]; // 16KB buffer\n while ((nRead = is.read(data, 0, data.length)) != -1) {\n buffer.write(data, 0, nRead);\n }\n buffer.flush();\n byte[] bytes = buffer.toByteArray();\n return Base64.encodeToString(bytes, Base64.NO_WRAP);\n}\n\n private boolean loadBmpFromFile(String path, int alpha, Window w) {\n try (FileInputStream fis = new FileInputStream(path)) {\n Bitmap bmp = BitmapFactory.decodeStream(fis);\n if (bmp != null) {\n w.getDecorView().setBackground(new AspectDrawable(bmp, 0xFFF0F0F0, alpha));\n return true;\n }\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"File load failed: \" + path, e);\n }\n return false;\n }\n\n // 新增:API 缓存逻辑\n private boolean loadBmpFromApiCached(PluginContext c, String url, int alpha, Window w) {\n try {\n File cacheDir = new File(c.getFilesDir(), \"bg_cache\");\n if (!cacheDir.exists()) cacheDir.mkdirs();\n \n File next = new File(cacheDir, \"next.jpg\");\n File curr = new File(cacheDir, \"curr.jpg\");\n \n // 1. 如果有缓存的下一张,挪到当前使用,实现“预加载”效果\n if (next.exists() && next.length() > 0) {\n if (curr.exists()) curr.delete();\n next.renameTo(curr);\n }\n \n boolean loaded = false;\n \n // 2. 尝试加载当前图片\n if (curr.exists() && curr.length() > 0) {\n loaded = loadBmpFromFile(curr.getAbsolutePath(), alpha, w);\n }\n \n // 3. 如果没加载成功(无缓存或文件损坏),则使用旧的在线加载方式作为兜底(会慢)\n if (!loaded) {\n next.delete();\n // 暂时返回 false 让上层回退到默认背景,保证界面立刻响应\n // 同时触发下载任务\n }\n \n // 4. 无论如何,开启线程预下载下一张图片,供下次使用\n new Thread(() -> {\n try {\n downloadFile(url, next);\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"Download bg failed\", e);\n }\n }).start();\n \n return loaded;\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"API cache load failed\", e);\n return false;\n }\n }\n\n private void downloadFile(String urlStr, File target) throws Exception {\n HttpURLConnection conn = (HttpURLConnection) new URL(urlStr).openConnection();\n try {\n conn.setConnectTimeout(10000);\n conn.setReadTimeout(10000);\n conn.setDoInput(true);\n conn.setInstanceFollowRedirects(true);\n conn.setRequestProperty(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\");\n conn.connect();\n \n // Ensure parent directory exists\n File parentDir = target.getParentFile();\n if (parentDir != null && !parentDir.exists()) {\n parentDir.mkdirs();\n }\n \n // 先下载到临时文件,下载完成后再重命名,防止文件损坏\n File tmp = new File(parentDir, target.getName() + \"_\" + UUID.randomUUID() + \".tmp\");\n try (InputStream is = conn.getInputStream(); FileOutputStream fos = new FileOutputStream(tmp)) {\n byte[] buffer = new byte[8192];\n int len;\n while ((len = is.read(buffer)) != -1) {\n fos.write(buffer, 0, len);\n }\n }\n \n // Use Files.move for atomic operation if possible, fallback to delete+rename\n if (target.exists()) {\n if (!target.delete()) {\n // Try to force delete or handle error? For now log/ignore\n new O.L(c).e(\"IO\", \"Target delete failed: \" + target, null);\n }\n }\n if (!tmp.renameTo(target)) {\n // Fallback: Copy and delete\n try {\n Files.copy(tmp.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);\n tmp.delete();\n } catch (Exception e) {\n tmp.delete();\n throw new IOException(\"Failed to move temp file to target: \" + e.getMessage(), e);\n }\n }\n } finally {\n if (conn != null) conn.disconnect();\n }\n }\n \n // 保持兼容旧代码\n private void loadBmpFromApi(String url, int alpha, Window w) {\n // 此方法已弃用,保留为空或重定向\n }\n\n private boolean loadBmpFromLocalLibrary(int alpha, Window w) {\n try {\n File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);\n File dir = new File(downloadsDir, \"MT_AI_BG\");\n if (dir.exists() && dir.isDirectory()) {\n File[] files = dir.listFiles((d, name) -> \n name.toLowerCase().endsWith(\".jpg\") || \n name.toLowerCase().endsWith(\".png\") || \n name.toLowerCase().endsWith(\".webp\"));\n \n if (files != null && files.length > 0) {\n File randomFile = files[new Random().nextInt(files.length)];\n return loadBmpFromFile(randomFile.getAbsolutePath(), alpha, w);\n }\n }\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"Local lib load failed\", e);\n }\n return false;\n }\n\n private void schedulePeriodicUpdate(View rootView) {\n rootView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {\n private long lastUpdateTime = 0;\n private boolean isFinished = false;\n private View cachedScrollContainer = null; \n\n @Override\n public void onScrollChanged() {\n if (isFinished) return;\n\n long currentTime = System.currentTimeMillis();\n if (currentTime - lastUpdateTime >= 150) {\n processViewsLiquidGlass(rootView);\n lastUpdateTime = currentTime;\n\n if (cachedScrollContainer == null) {\n cachedScrollContainer = findRealScrollableContainer(rootView);\n }\n\n if (cachedScrollContainer == null) {\n terminate();\n } else {\n // if (!cachedScrollContainer.canScrollVertically(1)) {\n // terminate();\n // }\n }\n }\n }\n\n private void terminate() {\n isFinished = true;\n rootView.post(() -> {\n if (rootView.getViewTreeObserver().isAlive()) {\n rootView.getViewTreeObserver().removeOnScrollChangedListener(this);\n }\n });\n }\n });\n \n processViewsLiquidGlass(rootView);\n }\n\n /**\n * 深度递归搜索:找到真正能够滚动的那个 View\n */\n private View findRealScrollableContainer(View v) {\n if (v == null || v.getVisibility() != View.VISIBLE) return null;\n\n if (v.canScrollVertically(1) || v.canScrollVertically(-1)) {\n return v;\n }\n\n if (v instanceof ViewGroup) {\n ViewGroup group = (ViewGroup) v;\n for (int i = 0; i < group.getChildCount(); i++) {\n View found = findRealScrollableContainer(group.getChildAt(i));\n if (found != null) return found;\n }\n }\n return null;\n }\n\n private void processViewsLiquidGlass(View v) {\n if (v == null) return;\n SharedPreferences s = c.getPreferences();\n \n // 获取自定义颜色设置\n int colorTitle = s.getInt(\"color_title\", 0xFF000000);\n int colorSummary = s.getInt(\"color_summary\", 0xFF444444);\n int colorHeader = s.getInt(\"color_header\", 0xFF333333);\n int colorText = s.getInt(\"color_text\", 0xFF333333);\n int colorIcon = s.getInt(\"color_icon\", 0xFF1D1D1F);\n int listAlpha = s.getInt(\"bg_list_alpha\", 0x40); // 默认 0x40 (64)\n \n String idName = \"\";\n String className = v.getClass().getName();\n try { if (v.getId() != View.NO_ID) idName = v.getContext().getResources().getResourceEntryName(v.getId()); } catch (Exception e) {\n // ID 查找失败通常不重要,但如果需要调试可以记录\n new O.L(c).e(\"UI\", \"ID lookup failed\", e);\n }\n\n if (idName.equals(\"MT_Protector\")) {\n v.setBackgroundColor(Color.TRANSPARENT);\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { v.setElevation(0f); v.setOutlineProvider(null); }\n ViewParent p = v.getParent();\n if (p instanceof ViewGroup) {\n ViewGroup vg = (ViewGroup) p;\n vg.setBackgroundColor(Color.TRANSPARENT);\n for (int i = 0; i < vg.getChildCount(); i++) {\n View child = vg.getChildAt(i);\n if (child != v && child.getHeight() > 0 && child.getHeight() < 15) child.setVisibility(View.GONE);\n }\n }\n }\n\n boolean isRecyclerViewItem = false;\n ViewParent pCheck = v.getParent();\n while (pCheck != null) {\n if (pCheck.getClass().getName().contains(\"RecyclerView\")) { isRecyclerViewItem = true; break; }\n pCheck = pCheck.getParent();\n }\n\n if (v instanceof RelativeLayout || (isRecyclerViewItem && v instanceof ViewGroup && !className.contains(\"RecyclerView\") && !className.contains(\"TextView\"))) {\n // applyGlassCardStyle handles RenderEffect now\n applyGlassCardStyle(v, listAlpha);\n v.post(() -> {\n v.setPivotX(v.getWidth() / 2f);\n v.setPivotY(v.getHeight() / 2f);\n });\n }\n\n if (v instanceof TextView) {\n TextView tv = (TextView) v;\n String text = tv.getText().toString();\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) tv.setRenderEffect(null);\n\n if (text.equals(\"AI文本分析助手设置\")) {\n tv.setTextColor(colorTitle);\n tv.setTypeface(Typeface.create(\"sans-serif-medium\", Typeface.BOLD));\n tv.setShadowLayer(5, 0, 0, Color.WHITE);\n } else if (idName.equals(\"title\")) {\n tv.setTextColor(colorText);\n } else if (idName.equals(\"summary\")) {\n tv.setTextColor(colorSummary);\n } else {\n tv.setTextColor(colorHeader);\n }\n }\n\n if (v instanceof ImageButton) {\n CharSequence desc = v.getContentDescription();\n if (desc != null && desc.toString().contains(\"上一层级\")) {\n ImageButton ib = (ImageButton) v;\n ib.setColorFilter(colorIcon, PorterDuff.Mode.SRC_IN);\n GradientDrawable circle = new GradientDrawable();\n circle.setShape(GradientDrawable.OVAL);\n circle.setColor(0x20000000);\n v.setBackground(new RippleDrawable(ColorStateList.valueOf(0x30FFFFFF), null, circle));\n }\n }\n\n if (className.contains(\"Switch\") || v instanceof CompoundButton) {\n // Remove RenderEffect clear to allow global effects if any\n \n int sThumbOn = s.getInt(\"color_sw_thumb_on\", 0xFF2196F3);\n int sThumbOff = s.getInt(\"color_sw_thumb_off\", 0xFFBBBBBB);\n int sTrackOn = s.getInt(\"color_sw_track_on\", 0x662196F3);\n int sTrackOff = s.getInt(\"color_sw_track_off\", 0x4D000000);\n\n int[][] states = new int[][]{ new int[]{-android.R.attr.state_checked}, new int[]{ android.R.attr.state_checked} };\n ColorStateList thumbTint = new ColorStateList(states, new int[]{sThumbOff, sThumbOn});\n ColorStateList trackTint = new ColorStateList(states, new int[]{sTrackOff, sTrackOn});\n\n try {\n Method setThumbTint = v.getClass().getMethod(\"setThumbTintList\", ColorStateList.class);\n Method setTrackTint = v.getClass().getMethod(\"setTrackTintList\", ColorStateList.class);\n setThumbTint.invoke(v, thumbTint);\n setTrackTint.invoke(v, trackTint);\n } catch (Exception e) {\n if (v instanceof CompoundButton && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) \n ((CompoundButton) v).setButtonTintList(thumbTint);\n new O.L(c).e(\"UI\", \"Switch tint failed\", e);\n }\n }\n\n if (v instanceof ViewGroup) {\n ViewGroup g = (ViewGroup) v;\n for (int i = 0; i < g.getChildCount(); i++) processViewsLiquidGlass(g.getChildAt(i));\n }\n }\n\n private void applyGlassCardStyle(View v, int listAlpha) {\n if (v.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {\n ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();\n lp.setMargins(30, 15, 30, 15);\n v.setLayoutParams(lp);\n }\n\n // 修复:复用 LiquidGlassDrawable\n if (v.getBackground() instanceof LiquidGlassDrawable) {\n ((LiquidGlassDrawable) v.getBackground()).updateParams(listAlpha);\n } else {\n LiquidGlassDrawable bg = new LiquidGlassDrawable(listAlpha);\n bg.setView(v);\n v.setBackground(bg);\n }\n \n // 关键修改:减少 padding,让子 View 有“收缩空间”\n v.setPadding(40, 40, 40, 40); // 原 40 → 改 20 或 16\n\n // 关键:强制设置缩放中心为 View 中心\n v.setPivotX(v.getWidth() / 2f);\n v.setPivotY(v.getHeight() / 2f);\n\n // 可选:提升动画流畅度\n v.setLayerType(View.LAYER_TYPE_HARDWARE, null);\n \n // 尝试使用 RenderEffect (仅 Android 12+)\n if (Build.VERSION.SDK_INT >= 31) {\n v.setRenderEffect(null);\n }\n \n // 4. 点击缩放特效\n addClickScaleEffect(v);\n \n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n v.setForeground(new RippleDrawable(ColorStateList.valueOf(0x15FFFFFF), null, null));\n }\n }\n\n private void addClickScaleEffect(View v) {\n if (v.getTag(0xDEADBEEF) != null) return;\n v.setTag(0xDEADBEEF, \"scaled\");\n // v.setClickable(true); // 保持注释状态,通过 Drawable 响应状态变化来实现回弹\n\n // 移除 OnTouchListener,改由 LiquidGlassDrawable 的 onStateChange 驱动\n // 这样既能兼容 ListView 的点击(不拦截),又能兼容普通按钮的点击\n }\n\n /**\n * iOS 26 液态玻璃风格 Drawable (精简版 - 移除流光/模糊)\n */\n public static class LiquidGlassDrawable extends Drawable {\n private Paint mPaint;\n private Paint mBorderPaint;\n private Paint mHighlightPaint;\n \n private float mRadius = 45f; // 对应 2rem (约 30-40dp)\n \n private int mBaseAlpha;\n private boolean mIsPressed = false;\n \n // 新增:持有 View 的弱引用以控制动画\n private WeakReference mViewRef;\n\n public LiquidGlassDrawable(int alpha) {\n init(alpha);\n }\n \n // 新增:设置关联 View\n public void setView(View v) {\n this.mViewRef = new WeakReference<>(v);\n }\n\n void init(int alpha) {\n mBaseAlpha = alpha;\n \n mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n mPaint.setColor(Color.WHITE);\n mPaint.setAlpha(alpha);\n mPaint.setStyle(Paint.Style.FILL);\n\n mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n mBorderPaint.setStyle(Paint.Style.STROKE);\n mBorderPaint.setStrokeWidth(3f);\n \n mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n mHighlightPaint.setStyle(Paint.Style.FILL);\n }\n\n // 新增:更新参数\n public void updateParams(int alpha) {\n if (mBaseAlpha != alpha) {\n init(alpha);\n invalidateSelf();\n }\n }\n\n @Override\n public boolean isStateful() {\n return true;\n }\n\n @Override\n protected boolean onStateChange(int[] state) {\n boolean pressed = false;\n for (int s : state) {\n if (s == android.R.attr.state_pressed) {\n pressed = true;\n break;\n }\n }\n \n setPressed(pressed);\n \n // 触发 View 动画\n if (mViewRef != null) {\n View v = mViewRef.get();\n if (v != null) {\n v.animate().cancel();\n\n if (pressed) {\n v.animate()\n .scaleX(0.95f)\n .scaleY(0.95f)\n .setDuration(150)\n .setInterpolator(new android.view.animation.DecelerateInterpolator())\n .withLayer()\n .start();\n } else {\n v.animate()\n .scaleX(1f)\n .scaleY(1f)\n .setDuration(300)\n .setInterpolator(new android.view.animation.OvershootInterpolator(1.5f))\n .withLayer()\n .start();\n }\n }\n }\n \n return super.onStateChange(state);\n }\n\n public void setPressed(boolean pressed) {\n if (mIsPressed != pressed) {\n mIsPressed = pressed;\n invalidateSelf();\n }\n }\n\n @Override\n public void draw(@NonNull Canvas canvas) {\n Rect b = getBounds();\n float w = b.width();\n float h = b.height();\n if (w <= 0 || h <= 0) return;\n \n // 动态调整圆角 (Hover/Press 效果)\n float currentRadius = mIsPressed ? mRadius * 1.2f : mRadius;\n\n // 1. 绘制玻璃本体 (Tint)\n mPaint.setColor(Color.WHITE);\n mPaint.setAlpha(mIsPressed ? mBaseAlpha + 20 : mBaseAlpha); // 按下变亮\n canvas.drawRoundRect(new RectF(b), currentRadius, currentRadius, mPaint);\n\n // 2. 绘制高光 (Shine - Inset Shadow simulation)\n\n // 顶部高光\n LinearGradient topShine = new LinearGradient(0, 0, 0, h/3,\n 0x80FFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);\n mHighlightPaint.setShader(topShine);\n canvas.drawRoundRect(new RectF(b.left+2, b.top+2, b.right-2, b.bottom-2), currentRadius, currentRadius, mHighlightPaint);\n \n // 左上角强烈反光\n RadialGradient spotShine = new RadialGradient(0, 0, w, \n 0x60FFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);\n mHighlightPaint.setShader(spotShine);\n canvas.drawRoundRect(new RectF(b), currentRadius, currentRadius, mHighlightPaint);\n\n // 3. 绘制边框 (Border)\n // 渐变边框:左上白,右下透\n LinearGradient borderGrad = new LinearGradient(0, 0, w, h,\n new int[]{0xCCFFFFFF, 0x20FFFFFF, 0x40FFFFFF},\n null, Shader.TileMode.CLAMP);\n mBorderPaint.setShader(borderGrad);\n canvas.drawRoundRect(new RectF(b), currentRadius, currentRadius, mBorderPaint);\n }\n\n @Override\n public void setAlpha(int alpha) { mPaint.setAlpha(alpha); }\n @Override\n public void setColorFilter(@Nullable ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); }\n @Override\n public int getOpacity() { return PixelFormat.TRANSLUCENT; }\n }\n\n /**\n * 自定义液态玻璃 Dialog\n * 替代系统 Dialog,实现完全自定义的 UI\n */\n public static class LiquidDialog {\n private final Context context;\n private final android.app.Dialog dialog;\n private final LinearLayout root;\n private final TextView titleView;\n private final FrameLayout contentContainer;\n private final LinearLayout buttonBar;\n private final ScrollView scrollView;\n private final PluginContext pluginContext;\n \n private static class BtnConfig {\n String text;\n bin.mt.plugin.api.ui.dialog.PluginDialog.OnClickListener listener;\n String type;\n BtnConfig(String t, bin.mt.plugin.api.ui.dialog.PluginDialog.OnClickListener l, String type) { text=t; listener=l; this.type=type; }\n }\n private java.util.Map buttons = new java.util.HashMap<>();\n private java.util.List currentOrder = new java.util.ArrayList<>();\n\n public LiquidDialog(PluginUI ui) {\n Context ctx = null;\n try {\n Object pc = ui.getContext();\n if (pc instanceof Context) ctx = (Context) pc;\n } catch (Exception e) {\n if (w.weakContext != null && w.weakContext.get() != null) {\n new O.L(w.weakContext.get()).e(\"UI\", \"Context lookup failed\", e);\n }\n }\n if (ctx == null) ctx = new w().findActivity();\n if (ctx == null && w.weakContext != null) {\n Object obj = w.weakContext.get();\n if (obj instanceof Context) ctx = (Context) obj;\n }\n if (ctx == null) {\n try {\n Class atClass = Class.forName(\"android.app.ActivityThread\");\n ctx = (Context) atClass.getMethod(\"currentApplication\").invoke(null);\n } catch (Exception e) {}\n }\n if (ctx == null) throw new RuntimeException(\"Cannot find Activity Context\");\n\n this.context = ctx;\n \n PluginContext pcFromUi = null;\n try {\n if (ui.getContext() instanceof PluginContext) pcFromUi = (PluginContext) ui.getContext();\n } catch (Exception e) {}\n \n this.pluginContext = pcFromUi != null ? pcFromUi : (w.weakContext != null ? w.weakContext.get() : null);\n \n dialog = new android.app.Dialog(context);\n dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);\n if (dialog.getWindow() != null) {\n // Fix: Set type for non-Activity context (e.g. Floating Window)\n if (!(context instanceof android.app.Activity)) {\n if (Build.VERSION.SDK_INT >= 26) {\n dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);\n } else {\n dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_PHONE);\n }\n }\n dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));\n dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n dialog.getWindow().setDimAmount(0.5f);\n }\n\n int bgAlpha = 230;\n int titleColor = Color.BLACK;\n if (pluginContext != null) {\n SharedPreferences s = pluginContext.getPreferences();\n bgAlpha = s.getInt(\"bg_dialog_alpha\", 230);\n if (bgAlpha < 50) bgAlpha = 230;\n titleColor = s.getInt(\"color_title\", Color.BLACK);\n \n String orderStr = s.getString(\"dialog_btn_order\", \"NEU,NEG,POS\");\n if(orderStr != null && !orderStr.isEmpty()) {\n String[] parts = orderStr.split(\",\");\n for(String p : parts) currentOrder.add(p.trim());\n }\n }\n if(currentOrder.isEmpty()) {\n currentOrder.add(\"NEU\"); currentOrder.add(\"SPRING\"); currentOrder.add(\"NEG\"); currentOrder.add(\"POS\");\n }\n\n root = new LinearLayout(context);\n root.setOrientation(LinearLayout.VERTICAL);\n LiquidGlassDrawable bg = new LiquidGlassDrawable(bgAlpha); \n root.setBackground(bg);\n root.setPadding(50, 50, 50, 50);\n \n titleView = new TextView(context);\n titleView.setTextSize(18);\n titleView.setTypeface(Typeface.DEFAULT_BOLD);\n titleView.setTextColor(titleColor); \n titleView.setPadding(0, 0, 0, 20);\n titleView.setVisibility(View.GONE);\n root.addView(titleView);\n\n scrollView = new ScrollView(context);\n scrollView.setVerticalScrollBarEnabled(false);\n scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);\n \n contentContainer = new FrameLayout(context);\n scrollView.addView(contentContainer);\n \n LinearLayout.LayoutParams scrollLp = new LinearLayout.LayoutParams(\n ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f\n );\n root.addView(scrollView, scrollLp);\n\n buttonBar = new LinearLayout(context);\n buttonBar.setOrientation(LinearLayout.HORIZONTAL);\n buttonBar.setGravity(Gravity.END);\n buttonBar.setWeightSum(3.0f);\n buttonBar.setPadding(0, 20, 0, 0);\n root.addView(buttonBar);\n\n dialog.setContentView(root);\n }\n\n public LiquidDialog setPadding(int left, int top, int right, int bottom) {\n root.setPadding(left, top, right, bottom);\n buttonBar.setPadding(20, 20, 20, 20);\n titleView.setPadding(20, 20, 20, 20);\n return this;\n }\n\n public LiquidDialog setTitle(CharSequence title) {\n titleView.setText(title);\n titleView.setVisibility(View.VISIBLE);\n return this;\n }\n\n public LiquidDialog setMessage(CharSequence message) {\n TextView tv = new TextView(context);\n tv.setText(message);\n tv.setTextColor(Color.DKGRAY);\n tv.setTextSize(14);\n if (pluginContext != null) {\n SharedPreferences s = pluginContext.getPreferences();\n tv.setTextColor(s.getInt(\"color_text\", Color.DKGRAY));\n }\n contentContainer.removeAllViews();\n contentContainer.addView(tv);\n return this;\n }\n\n public LiquidDialog setView(Object view) {\n View v = O.getRealView(view);\n if (v == null && view instanceof View) v = (View) view;\n \n if (v != null) {\n contentContainer.removeAllViews();\n if (v.getParent() != null) ((ViewGroup)v.getParent()).removeView(v);\n \n ViewGroup.LayoutParams lp = v.getLayoutParams();\n FrameLayout.LayoutParams flp;\n if (lp instanceof ViewGroup.MarginLayoutParams) {\n flp = new FrameLayout.LayoutParams(lp.width, lp.height);\n flp.setMargins(((ViewGroup.MarginLayoutParams)lp).leftMargin, \n ((ViewGroup.MarginLayoutParams)lp).topMargin,\n ((ViewGroup.MarginLayoutParams)lp).rightMargin,\n ((ViewGroup.MarginLayoutParams)lp).bottomMargin);\n } else if (lp != null) {\n flp = new FrameLayout.LayoutParams(lp);\n } else {\n flp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n }\n v.setLayoutParams(flp);\n \n contentContainer.addView(v);\n }\n return this;\n }\n\n public LiquidDialog setCancelable(boolean flag) {\n dialog.setCancelable(flag);\n return this;\n }\n\n public LiquidDialog setPositiveButton(String text, bin.mt.plugin.api.ui.dialog.PluginDialog.OnClickListener listener) {\n buttons.put(\"POS\", new BtnConfig(text, listener, \"POS\"));\n return this;\n }\n\n public LiquidDialog setNegativeButton(String text, bin.mt.plugin.api.ui.dialog.PluginDialog.OnClickListener listener) {\n buttons.put(\"NEG\", new BtnConfig(text, listener, \"NEG\"));\n return this;\n }\n\n public LiquidDialog setNeutralButton(String text, bin.mt.plugin.api.ui.dialog.PluginDialog.OnClickListener listener) {\n buttons.put(\"NEU\", new BtnConfig(text, listener, \"NEU\"));\n return this;\n }\n \n public LiquidDialog setItems(String[] items, bin.mt.plugin.api.ui.dialog.PluginDialog.OnClickListener listener) {\n LinearLayout list = new LinearLayout(context);\n list.setOrientation(LinearLayout.VERTICAL);\n int textColor = Color.BLACK;\n if (pluginContext != null) textColor = pluginContext.getPreferences().getInt(\"color_text\", Color.BLACK);\n \n for (int i = 0; i < items.length; i++) {\n final int index = i;\n TextView tv = new TextView(context);\n tv.setText(items[i]);\n tv.setPadding(20, 30, 20, 30);\n tv.setTextSize(16);\n tv.setTextColor(textColor);\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n tv.setBackground(new android.graphics.drawable.RippleDrawable(ColorStateList.valueOf(0x20000000), null, null));\n }\n tv.setOnClickListener(v -> {\n if (listener != null) listener.onClick(null, index);\n dialog.dismiss();\n });\n list.addView(tv);\n }\n contentContainer.removeAllViews();\n contentContainer.addView(list);\n return this;\n }\n\n private int getButtonColor(int slotIndex) {\n if (pluginContext == null) return Color.GRAY;\n SharedPreferences s = pluginContext.getPreferences();\n if (slotIndex == 0) return s.getInt(\"color_btn_neu\", Color.LTGRAY);\n if (slotIndex == 1) return s.getInt(\"color_btn_neg\", Color.GRAY);\n return s.getInt(\"color_btn_pos\", Color.parseColor(\"#2196F3\"));\n }\n\n public android.app.Dialog show() {\n renderButtons();\n try {\n dialog.show();\n Window w = dialog.getWindow();\n if (w != null) {\n android.util.DisplayMetrics dm = context.getResources().getDisplayMetrics();\n w.setLayout((int)(dm.widthPixels * 0.85), ViewGroup.LayoutParams.WRAP_CONTENT);\n }\n } catch (Exception e) {\n if (pluginContext != null) new O.L(pluginContext).e(\"UI\", \"Dialog show failed\", e);\n }\n return dialog;\n }\n\n private void renderButtons() {\n buttonBar.removeAllViews();\n boolean dragEnabled = pluginContext != null && pluginContext.getPreferences().getBoolean(\"dialog_drag_enabled\", false);\n \n // 确保 currentOrder 包含所有存在的按钮类型\n for(String type : buttons.keySet()) {\n if(!currentOrder.contains(type)) currentOrder.add(type);\n }\n\n for(String type : currentOrder) {\n if (type.equals(\"SPRING\")) {\n View spring = new View(context);\n LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, 0, 1.0f);\n spring.setLayoutParams(lp);\n buttonBar.addView(spring);\n continue;\n }\n\n if (!buttons.containsKey(type)) continue;\n\n BtnConfig b = buttons.get(type);\n TextView btn = new TextView(context);\n btn.setText(b.text);\n \n int colorIndex = 0; // default\n if (type.equals(\"NEG\")) colorIndex = 1;\n else if (type.equals(\"POS\")) colorIndex = 2;\n btn.setTextColor(getButtonColor(colorIndex));\n \n btn.setTypeface(Typeface.DEFAULT_BOLD);\n btn.setGravity(Gravity.CENTER);\n btn.setPadding(30, 20, 30, 20);\n\n // 如果有 Spring,按钮使用 wrap_content,否则平分\n boolean hasSpring = currentOrder.contains(\"SPRING\");\n LinearLayout.LayoutParams lp;\n if (hasSpring) {\n lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n lp.weight = 0;\n // 给按钮之间加点间距\n lp.leftMargin = 10;\n lp.rightMargin = 10;\n } else {\n lp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1.0f);\n }\n btn.setLayoutParams(lp);\n\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n btn.setBackground(new android.graphics.drawable.RippleDrawable(ColorStateList.valueOf(0x20000000), null, null));\n }\n \n btn.setOnClickListener(v -> {\n if (b.listener != null) b.listener.onClick(null, 0); \n dialog.dismiss();\n });\n\n if (dragEnabled) {\n btn.setOnLongClickListener(v -> {\n View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);\n if (Build.VERSION.SDK_INT >= 24) {\n v.startDragAndDrop(null, shadow, b.type, 0);\n } else {\n v.startDrag(null, shadow, b.type, 0);\n }\n return true;\n });\n \n btn.setOnDragListener((v, event) -> {\n switch (event.getAction()) {\n case DragEvent.ACTION_DROP:\n Object localState = event.getLocalState();\n if(localState instanceof String) {\n String srcType = (String) localState;\n String destType = b.type;\n if (!srcType.equals(destType)) {\n swapOrder(srcType, destType);\n }\n }\n return true;\n }\n return true;\n });\n }\n buttonBar.addView(btn);\n }\n }\n \n private void swapOrder(String src, String dest) {\n int idx1 = currentOrder.indexOf(src);\n int idx2 = currentOrder.indexOf(dest);\n if(idx1 != -1 && idx2 != -1) {\n Collections.swap(currentOrder, idx1, idx2);\n if(pluginContext != null) {\n StringBuilder sb = new StringBuilder();\n for(int i=0; i {\n // 通过 vArr[0] 安全引用已构建的视图\n if (vArr[0] != null) {\n String currentApi = vArr[0].requireViewById(\"api_url\").getText().toString().trim();\n cacheFromApi(c, currentApi.isEmpty() ? \"https://yingciyuan.cn/pe.php\" : currentApi);\n }\n })\n\n // --- 透明度设置 (保留原样) ---\n .addTextView().text(\"透明度设置(值越大越不透明):\").bold().marginTopDp(15)\n .addTextView().text(\"整体背景透明度 (0-255):\").textSize(12)\n .addEditText(\"alpha\").text(String.valueOf(s.getInt(\"bg_alpha\", 0))).inputType(2)\n .addTextView().text(\"菜单列表透明度 (0-255):\").textSize(12)\n .addEditText(\"l_alpha\").text(String.valueOf(s.getInt(\"bg_list_alpha\", 64))).inputType(2)\n .addTextView().text(\"对话框背景透明度 (50-255):\").textSize(12)\n .addEditText(\"d_alpha\").text(String.valueOf(s.getInt(\"bg_dialog_alpha\", 230))).inputType(2)\n \n // --- 颜色定制 (保留原样字符串与逻辑) ---\n .addTextView().text(\"界面颜色定制 (Hex: #AARRGGBB):\").bold().marginTopDp(15)\n .addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"返回图标颜色:\")\n .addEditText(\"c_icon\").text(String.format(\"#%08X\", s.getInt(\"color_icon\", 0xFF1D1D1F))).hint(\"#FF1D1D1F\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"设置标题颜色:\")\n .addEditText(\"c_title\").text(String.format(\"#%08X\", s.getInt(\"color_title\", 0xFF000000))).hint(\"#FF000000\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"菜单分类颜色:\")\n .addEditText(\"c_header\").text(String.format(\"#%08X\", s.getInt(\"color_header\", 0xFF333333))).hint(\"#FF333333\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"菜单标题颜色:\")\n .addEditText(\"c_text\").text(String.format(\"#%08X\", s.getInt(\"color_text\", 0xFF333333))).hint(\"#FF333333\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"菜单摘要颜色:\")\n .addEditText(\"c_summary\").text(String.format(\"#%08X\", s.getInt(\"color_summary\", 0xFF444444))).hint(\"#FF444444\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"滑块开启颜色:\")\n .addEditText(\"c_sw_on\").text(String.format(\"#%08X\", s.getInt(\"color_sw_thumb_on\", 0xFF2196F3))).hint(\"#FF2196F3\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"滑轨开启颜色:\")\n .addEditText(\"c_sw_r_on\").text(String.format(\"#%08X\", s.getInt(\"color_sw_track_on\", 0x662196F3))).hint(\"#662196F3\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"滑块关闭颜色:\")\n .addEditText(\"c_sw_off\").text(String.format(\"#%08X\", s.getInt(\"color_sw_thumb_off\", 0xFFBBBBBB))).hint(\"#FFBBBBBB\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"滑轨关闭颜色:\")\n .addEditText(\"c_sw_r_off\").text(String.format(\"#%08X\", s.getInt(\"color_sw_track_off\", 0x4D000000))).hint(\"#4D000000\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"积极按钮颜色:\")\n .addEditText(\"c_btn_pos\").text(String.format(\"#%08X\", s.getInt(\"color_btn_pos\", 0xFF2196F3))).hint(\"#FF2196F3\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"消极按钮颜色:\")\n .addEditText(\"c_btn_neg\").text(String.format(\"#%08X\", s.getInt(\"color_btn_neg\", 0xFF888888))).hint(\"#FF888888\")\n ).addHorizontalLayout().children(subBuilder -> subBuilder\n .addTextView().text(\"中性按钮颜色:\")\n .addEditText(\"c_btn_neu\").text(String.format(\"#%08X\", s.getInt(\"color_btn_neu\", 0xFFCCCCCC))).hint(\"#FFCCCCCC\")\n );\n\n // --- 流光特效设置 ---\n\n // 完成构建并赋值给数组\n vArr[0] = builder.build();\n PluginView v = vArr[0];\n\n // --- 动态显隐逻辑 ---\n PluginSpinner modeSpinner = v.requireViewById(\"mode\");\n\n \n // --- 动态显隐逻辑 ---\n File cacheDir = new File(c.getFilesDir(), \"bg_cache\");\n if (!cacheDir.exists()) {\n cacheDir.mkdirs(); // 确保目录存在\n }\n File next = new File(cacheDir, \"next.jpg\");\n Runnable updateVisibility = () -> {\n int mode = modeSpinner.getSelection();\n // 模式:0内置, 1本地, 2API, 3本地库\n v.requireViewById(\"h_local\").setVisibility(mode == 1 ? View.VISIBLE : View.GONE);\n v.requireViewById(\"local_path\").setVisibility(mode == 1 ? View.VISIBLE : View.GONE);\n v.requireViewById(\"h_api\").setVisibility(mode == 2 ? View.VISIBLE : View.GONE);\n v.requireViewById(\"api_url\").setVisibility(mode == 2 ? View.VISIBLE : View.GONE);\n v.requireViewById(\"h_cache\").setVisibility(mode == 2 ? View.VISIBLE : View.GONE);\n v.requireViewById(\"cache\").setVisibility(mode == 2 ? View.VISIBLE : View.GONE);\n\n if (mode == 2) {\n // 从输入框实时获取 URL(去掉首尾空格)\n String url = vArr[0].requireViewById(\"api_url\").getText().toString().trim();\n\n // 简单校验 URL 不为空(可根据需要加强校验)\n if (!url.isEmpty() && !(next.exists() && next.length() > 0)) {\n new Thread(() -> {\n try {\n downloadFile(url, next); // 假设你的 downloadFile 第二个参数是文件路径字符串\n // 下载成功后可刷新 UI(如更新图片预览)\n // 示例:在主线程更新\n // ((Activity) c).runOnUiThread(() -> { /* 更新界面 */ });\n } catch (Exception e) {\n new O.L(c).e(\"IO\", \"Download failed: \" + url, e);\n next.delete();\n // 可选:主线程提示用户下载失败\n // ((Activity) c).runOnUiThread(() -> Toast.makeText(c, \"图片下载失败\", Toast.LENGTH_SHORT).show());\n }\n }).start();\n }\n }\n\n };\n modeSpinner.setOnItemSelectedListener((sp, pos) -> updateVisibility.run());\n updateVisibility.run();\n\n new LiquidDialog(i).setTitle(\"界面视觉深度定制\").setView(v).setPositiveButton(\"保存并应用\", (d, w) -> {\n try {\n SharedPreferences.Editor e = s.edit();\n int selectedMode = modeSpinner.getSelection();\n e.putInt(\"bg_mode\", selectedMode);\n \n // 拆分保存\n String localP = v.requireViewById(\"local_path\").getText().toString().trim();\n String apiU = v.requireViewById(\"api_url\").getText().toString().trim();\n e.putString(\"bg_local_path\", localP);\n e.putString(\"bg_api_url\", apiU);\n \n\n // 验证并保存透明度\n int alpha = Integer.parseInt(v.requireViewById(\"alpha\").getText().toString());\n if (alpha < 0 || alpha > 255) throw new IllegalArgumentException(\"整体透明度必须在 0-255 之间\");\n e.putInt(\"bg_alpha\", alpha);\n\n int lAlpha = Integer.parseInt(v.requireViewById(\"l_alpha\").getText().toString());\n if (lAlpha < 0 || lAlpha > 255) throw new IllegalArgumentException(\"菜单列表透明度必须在 0-255 之间\");\n e.putInt(\"bg_list_alpha\", lAlpha);\n\n int dAlpha = Integer.parseInt(v.requireViewById(\"d_alpha\").getText().toString());\n if (dAlpha < 0 || dAlpha > 255) throw new IllegalArgumentException(\"对话框透明度必须在 0-255 之间\");\n e.putInt(\"bg_dialog_alpha\", dAlpha);\n \n // 颜色部分 (增加校验)\n String[] colorKeys = {\"color_icon\", \"color_title\", \"color_header\", \"color_text\", \"color_summary\", \"color_sw_thumb_on\", \"color_sw_track_on\", \"color_sw_thumb_off\", \"color_sw_track_off\", \"color_btn_pos\", \"color_btn_neg\", \"color_btn_neu\"};\n String[] colorIds = {\"c_icon\", \"c_title\", \"c_header\", \"c_text\", \"c_summary\", \"c_sw_on\", \"c_sw_r_on\", \"c_sw_off\", \"c_sw_r_off\", \"c_btn_pos\", \"c_btn_neg\", \"c_btn_neu\"};\n \n for(int k=0; krequireViewById(colorIds[k]).getText().toString().trim();\n if(!hex.matches(\"^#[0-9A-Fa-f]{8}$\")) throw new IllegalArgumentException(\"颜色格式错误 (\" + colorIds[k] + \"): 必须为 #AARRGGBB\");\n e.putInt(colorKeys[k], Color.parseColor(hex));\n }\n \n // 保存特效设置\n \n e.apply();\n i.showToast(\"设置已保存,请重新进入页面\");\n } catch (Exception ex) { \n i.showToast(\"输入格式错误: \" + ex.getMessage()); \n new O.L(c).e(\"UI\", \"Settings save failed\", ex);\n }\n }).setNegativeButton(\"取消\", null).show();\n}\n\n\n\n private void cacheFromApi(PluginContext ctx, String apiUrl) {\n Handler mainHandler = new Handler(Looper.getMainLooper());\n \n new Thread(() -> {\n try {\n // 1. 获取 Context\n Context appContext = (ctx instanceof Context) ? (Context) ctx : getGlobalContext();\n if (appContext == null) return;\n\n final Context finalContext = appContext;\n\n // 2. 准备下载\n DownloadManager manager = (DownloadManager) finalContext.getSystemService(Context.DOWNLOAD_SERVICE);\n\n mainHandler.post(() -> Toast.makeText(finalContext, \"开始后台缓存任务...\", Toast.LENGTH_SHORT).show());\n\n for (int i = 0; i < 5; i++) {\n DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apiUrl));\n \n // 伪装浏览器 Header (对抗 Cloudflare 418)\n request.addRequestHeader(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\");\n request.addRequestHeader(\"Referer\", \"https://lie.moe/\");\n \n request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);\n request.setTitle(\"AI背景缓存 \" + (i + 1));\n \n String fileName = \"BG_\" + System.currentTimeMillis() + \"_\" + i + \".jpg\";\n request.setDestinationInExternalPublicDir(android.os.Environment.DIRECTORY_DOWNLOADS, \"MT_AI_BG/\" + fileName);\n\n manager.enqueue(request);\n Thread.sleep(300); // 间隔请求,防止被封\n }\n\n mainHandler.post(() -> Toast.makeText(finalContext, \"5个任务已加入通知栏,请查看下载进度\", Toast.LENGTH_LONG).show());\n\n } catch (Exception e) {\n new O.L(ctx).e(\"UI\", \"Cache failed\", e);\n // 发生异常时,确保 Toast 的 Context 不为 null\n final String err = e.toString();\n mainHandler.post(() -> {\n // 这里的逻辑:如果异常了,我们尽量找一个不为 null 的 Context 弹窗\n try {\n Class atClass = Class.forName(\"android.app.ActivityThread\");\n Context c = (Context) atClass.getMethod(\"currentApplication\").invoke(null);\n if (c != null) Toast.makeText(c, \"严重异常: \" + err, Toast.LENGTH_LONG).show();\n } catch (Exception ignored) {\n new O.L(ctx).e(\"UI\", \"Toast context lookup failed\", ignored);\n }\n });\n }\n }).start();\n }\n\n private Activity findActivity() {\n try {\n Class threadClass = Class.forName(\"android.app.ActivityThread\");\n Object thread = threadClass.getMethod(\"currentActivityThread\").invoke(null);\n Field field = threadClass.getDeclaredField(\"mActivities\");\n field.setAccessible(true);\n Map activities = (Map) field.get(thread);\n for (Object record : activities.values()) {\n Field pausedField = record.getClass().getDeclaredField(\"paused\");\n pausedField.setAccessible(true);\n if (!pausedField.getBoolean(record)) {\n Field actField = record.getClass().getDeclaredField(\"activity\");\n actField.setAccessible(true);\n return (Activity) actField.get(record);\n }\n }\n } catch (Exception e) {\n if (c != null) new O.L(c).e(\"UI\", \"Activity find failed\", e);\n }\n return null;\n }\n\n class AspectDrawable extends Drawable {\n private Bitmap mBitmap; private Paint mPaint; private int mBgColor;\n AspectDrawable(Bitmap bitmap, int bgColor, int alpha) {\n this.mBitmap = bitmap; this.mBgColor = bgColor;\n this.mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);\n this.mPaint.setAlpha(alpha);\n }\n @Override\n public void draw(@NonNull Canvas canvas) {\n Rect b = getBounds();\n if (b.width() <= 0 || b.height() <= 0) return;\n canvas.drawColor(mBgColor);\n float vW = b.width(), vH = b.height(), bW = mBitmap.getWidth(), bH = mBitmap.getHeight(), scale, dx = 0, dy = 0;\n if (bW * vH > vW * bH) { scale = vH / bH; dx = (vW - bW * scale) * 0.5f; }\n else { scale = vW / bW; dy = (vH - bH * scale) * 0.5f; }\n Matrix m = new Matrix(); m.setScale(scale, scale); m.postTranslate(dx, dy);\n canvas.drawBitmap(mBitmap, m, mPaint);\n }\n @Override public void setAlpha(int a) { mPaint.setAlpha(a); }\n @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); }\n @Override public int getOpacity() { return PixelFormat.OPAQUE; }\n }\n\n // --- 以下代码保持原样 (S, u, g, K, U, P, p, u, a, C 方法) ---\n // 修改 S 方法以支持 Definition 更新\n void S(PluginUI i, SharedPreferences p, Object t, PluginPreference.Text def) {\n String u = O.gUrl(c); if (u.isEmpty()) u = P.get(0).u;\n String m = p.getString(O._0, \"\"), n = g(u);\n java.util.List l = new ArrayList<>();\n int x = 0;\n for (int k = 0; k < P.size(); k++) { l.add(P.get(k).n); if (P.get(k).n.equals(n)) x = k; }\n final int[] X = {x}; final int[] Y = {0};\n PluginView v = i.buildVerticalLayout()\n .addTextView().text(\"服务提供商:\").bold().paddingBottomDp(5)\n .addSpinner(\"s\").items(l).selection(x)\n .addEditText(\"u\").text(u).hint(\"输入 API URL\").singleLine(true).marginTopDp(5)\n .addTextView().text(\"模型选择:\").bold().paddingTopDp(15).paddingBottomDp(5)\n .addSpinner(\"m\")\n .addEditText(\"e\").text(m).hint(\"输入模型名称\").singleLine(true).marginTopDp(5).build();\n PluginSpinner S = v.requireViewById(\"s\"), M = v.requireViewById(\"m\");\n PluginEditText U = v.requireViewById(\"u\"), E = v.requireViewById(\"e\");\n Runnable R = () -> {\n int k = X[0]; p z = P.get(k); boolean isCustom = \"自定义提供商\".equals(z.n);\n if (isCustom) { U.setVisible(); if (U.getText().toString().isEmpty()) U.setText(\"https://\"); }\n else { U.setGone(); U.setText(z.u); }\n java.util.List ml = new ArrayList<>();\n if (!isCustom) Collections.addAll(ml, z.m);\n ml.add(\"自定义...\");\n M.setItems(ml);\n String tm = isCustom ? m : p.getString(O._0, \"\");\n int mi = ml.indexOf(tm); if (mi == -1) mi = ml.size() - 1;\n M.setSelection(mi); Y[0] = mi;\n if (isCustom || mi == ml.size() - 1) { E.setVisible(); if (!isCustom && E.getText().toString().equals(tm)) E.setText(\"\"); }\n else E.setGone();\n };\n S.setOnItemSelectedListener((sp, pos) -> { X[0] = pos; R.run(); E.setText(\"\"); });\n M.setOnItemSelectedListener((sp, pos) -> {\n Y[0] = pos; String sel = (String) sp.getItem(pos);\n if (\"自定义...\".equals(sel)) { E.setVisible(); E.requestFocus(); }\n else { E.setGone(); E.setText(sel); }\n });\n R.run();\n new LiquidDialog(i).setTitle(\"配置服务\").setView(v).setPositiveButton(\"保存\", (d, w) -> {\n String fu = U.getText().toString().trim(), fm = E.getText().toString().trim(), fn = P.get(X[0]).n;\n if (fu.isEmpty() || fm.isEmpty()) { i.showToast(\"URL 或 模型不能为空\"); return; }\n O.sUrl(c, fu); p.edit().putString(O._0, fm).apply();\n String newSummary = fn + \" / \" + fm;\n u(t, newSummary); \n if (def != null) def.summary(newSummary); // 更新 Definition 以持久化\n i.showToast(\"已保存配置\");\n if (p.getBoolean(\"api_auto_kv\", false)) {\n String key = O.g(c); if (!key.isEmpty()) {\n String r = \"自动添加 \" + new java.text.SimpleDateFormat(\"MM-dd HH:mm\").format(new Date());\n o.sk(i, -1, fn.equals(\"自定义提供商\") ? fu : fn, key, r, true);\n }\n }\n }).setNegativeButton(\"取消\", null).show();\n }\n\n // 保留旧签名的 S 方法以防万一\n void S(PluginUI i, SharedPreferences p, Object t) {\n S(i, p, t, null);\n }\n\n void u(Object o, String t) {\n try { Method m = o.getClass().getMethod(\"setSummary\", CharSequence.class); m.setAccessible(true); m.invoke(o, t); }\n catch (Exception e) { try { Method m = o.getClass().getMethod(\"setSummary\", String.class); m.setAccessible(true); m.invoke(o, t); } catch (Exception x) {\n new O.L(c).e(\"UI\", \"Summary update failed\", x);\n } }\n }\n\n String g(String u) { for (p p : P) if (p.u.equals(u)) return p.n; return \"自定义提供商\"; }\n\n interface I { void u(String s); }\n\n void K(PluginUI i, I u) {\n String k = O.g(c);\n boolean isDg = O.isDowngraded(c);\n PluginView v = i.buildVerticalLayout()\n .addTextView().text(isDg ? \"⚠️当前设备不支持硬件级加密,密钥将使用软件加密存储。\" : \"API Key 将加密存储在本地。\").textSize(12).textColor(isDg ? 0xFFFF0000 : 0xFF888888).paddingBottomDp(5)\n .addEditText(\"k\").text(k).hint(\"请输入 API 密钥\").inputType(129).singleLine(true).build();\n new LiquidDialog(i).setTitle(\"设置 API 密钥\").setView(v).setPositiveButton(\"保存\", (d, w) -> {\n String K_val = v.requireViewById(\"k\").getText().toString().trim();\n if (!K_val.isEmpty()) {\n O.s(c, K_val); \n boolean dg = O.isDowngraded(c);\n i.showToast(\"密钥已保存\" + (dg ? \" (⚠️降级模式)\" : \"\")); \n if (u != null) u.u(dg ? \"已存储 (⚠️安全降级模式)\" : \"已加密存储 (点击修改)\");\n if (c.getPreferences().getBoolean(\"api_auto_kv\", false)) {\n String ur = O.gUrl(c); String n = g(ur);\n String r = \"自动添加 \" + new java.text.SimpleDateFormat(\"MM-dd HH:mm\").format(new Date());\n o.sk(i, -1, n.equals(\"自定义提供商\") ? ur : n, K_val, r, true);\n }\n } else { O.s(c, \"\"); if (u != null) u.u(\"未设置 (点击设置)\"); }\n }).setNegativeButton(\"取消\", null).show();\n }\n\n void U(PluginUI i, String u) {\n for (p p : P) if (p.u.equals(u)) { if (p.k != null && !p.k.isEmpty()) c.openBrowser(p.k); else i.showToast(\"该提供商无预设注册链接\"); return; }\n i.showToast(\"自定义提供商请自行获取 API Key\");\n }\n\n void P(PluginUI i) {\n String j = c.getPreferences().getString(O.i, \"{}\");\n double t = 1.0, tp = 1.0; int mt = 4096; String ex = \"\";\n try {\n JSONObject obj = new JSONObject(j); t = obj.optDouble(\"temperature\", 1.0); tp = obj.optDouble(\"top_p\", 1.0); mt = obj.optInt(\"max_tokens\", 4096);\n obj.remove(\"temperature\"); obj.remove(\"top_p\"); obj.remove(\"max_tokens\"); if (obj.length() > 0) ex = obj.toString();\n } catch (Exception e) {\n new O.L(c).e(\"UI\", \"Params JSON parse failed\", e);\n }\n PluginRootLayoutBuilder b = i.buildVerticalLayout();\n b.addTextView().text(\"Temperature (0.0 - 2.0):\"); b.addEditText(\"t\").text(String.valueOf(t)).inputType(8194);\n b.addTextView().text(\"Top P (0.0 - 1.0):\"); b.addEditText(\"p\").text(String.valueOf(tp)).inputType(8194);\n b.addTextView().text(\"Max Tokens:\"); b.addEditText(\"m\").text(String.valueOf(mt)).inputType(2);\n b.addTextView().text(\"自定义 JSON 参数:\"); b.addEditText(\"e\").hint(\"例如 {\\\"thinking\\\":{\\\"type\\\":\\\"enabled\\\"}}\").text(ex).singleLine(false);\n PluginView v = b.build();\n new LiquidDialog(i).setTitle(\"参数设置\").setView(v).setPositiveButton(\"保存\", (d, w) -> {\n try {\n JSONObject obj = new JSONObject();\n String T = v.requireViewById(\"t\").getText().toString(), TP = v.requireViewById(\"p\").getText().toString(), MT = v.requireViewById(\"m\").getText().toString(), EX = v.requireViewById(\"e\").getText().toString();\n if (!T.isEmpty()) obj.put(\"temperature\", Double.parseDouble(T));\n if (!TP.isEmpty()) obj.put(\"top_p\", Double.parseDouble(TP));\n if (!MT.isEmpty()) obj.put(\"max_tokens\", Integer.parseInt(MT));\n if (!EX.isEmpty()) { JSONObject x = new JSONObject(EX); Iterator keys = x.keys(); while (keys.hasNext()) { String K_key = keys.next(); obj.put(K_key, x.get(K_key)); } }\n c.getPreferences().edit().putString(O.i, obj.toString()).apply(); i.showToast(\"参数已保存\");\n } catch (Exception e) { \n i.showToast(\"格式错误: \" + e.getMessage()); \n new O.L(c).e(\"UI\", \"Params save failed\", e);\n }\n }).setNegativeButton(\"取消\", null).show();\n }\n\n void p(PluginUI i) {\n String pStr = c.getPreferences().getString(O.l, \"请分析以下文本内容:\\n\\n{text}\");\n PluginView v = i.buildVerticalLayout().addTextView().text(\"提示: 使用 {text} 代表选中的文本。\").textSize(12).textColor(0xFF888888).addEditText(\"p\").text(pStr).singleLine(false).heightDp(150).build();\n new LiquidDialog(i).setTitle(\"编辑分析提示词\").setView(v).setPositiveButton(\"保存\", (d, w) -> {\n String P_val = v.requireViewById(\"p\").getText().toString();\n if (P_val.isEmpty()) P_val = \"请分析以下文本内容:\\n\\n{text}\";\n c.getPreferences().edit().putString(O.l, P_val).apply(); i.showToast(\"保存成功\");\n }).setNegativeButton(\"取消\", null).show();\n }\n\n void u(PluginUI i) {\n PluginView v = i.buildVerticalLayout()\n .addTextView().text(\"使用指南\").bold().textSize(18).textColor(0xFF8B4513).paddingBottomDp(10)\n .addTextView().text(\"1. 基础配置\").bold().marginTopDp(5)\n .addTextView().text(\" - 首次使用请点击“服务提供商”选择 AI 服务(推荐 SiliconFlow 或 DeepSeek)。\\n - 点击“获取 API Key”跳转官网注册获取密钥。\\n - 点击“API 密钥”输入密钥,插件会自动加密存储。\\n - 若需使用自定义服务,请选择“自定义提供商”并填入完整 API URL(如 https://api.example.com/v1/chat/completions)。\").textSize(13)\n .addTextView().text(\"2. 进阶设置\").bold().marginTopDp(5)\n .addTextView().text(\" - 模型参数:可调整 Temperature (随机性) 和 Max Tokens (回复长度)。支持自定义 JSON 参数以启用特定模型功能(如思维链)。\\n - 提示词:点击“分析提示词”可自定义 AI 的系统指令,{text} 为选中文本占位符。\\n - 界面定制:支持自定义背景图片(本地/API)、调整透明度及各部分字体颜色,打造专属液态玻璃 UI。\").textSize(13)\n .addTextView().text(\"3. 功能使用\").bold().marginTopDp(5)\n .addTextView().text(\" - 在文本编辑器中选中任意文本 -> 点击底部工具栏或悬浮球 -> 选择“AI文本分析”。\\n - 在弹出的对话框中,您可以直接查看分析结果,或继续输入问题进行追问。\\n - 支持历史对话记忆,开启“显示历史列表”后可随时接续之前的会话。\\n - 长按对话内容可复制,点击标题栏可导出对话记录。\").textSize(13)\n .addTextView().text(\"4. 密钥保管箱\").bold().marginTopDp(5)\n .addTextView().text(\" - 支持保存多组 API Key,方便在不同服务商之间快速切换。\\n - 开启“自动保存”后,新输入的 Key 会自动存入保管箱。\\n - 支持设置独立密码保护密钥安全。\").textSize(13)\n .addTextView().text(\"5. 核心架构\").bold().marginTopDp(5)\n .addTextView().text(\" - 对话UI管理: 本插件核心 UI 引擎,采用 WebView 混合渲染技术,内置高性能 Markdown 解析器模块。通过 JS Bridge 实现流式传输,支持 DeepSeek R1 等模型的思考过程 (Chain of Thought) 实时展示。独创的“对话树”可视化结构,允许用户在多次重新生成的内容间自由切换 (Branching)。\\n - 对话历史记录管理: 采用 SQLite 关系型数据库存储,并未简单的线性存储,而是完整记录了对话的树状拓扑结构。这不仅保证了多轮对话上下文的严谨性,还实现了对“撤回”、“重新生成”等操作的完美状态保存。支持导出为 HTML 格式,方便二次编辑与分享。\").textSize(13)\n .addTextView().text(\"6. 常见问题与故障排除\").bold().marginTopDp(5)\n .addTextView().text(\" - Q: 提示“密钥无效”?\\n A: 请检查 Key 是否正确,或服务商额度是否耗尽。\\n - Q: 界面卡顿或背景加载失败?\\n A: 请检查网络连接,或尝试关闭自定义背景功能。\\n - Q: 背景图片无法加载?\\n A: 插件已内置回退机制,若API或本地图片失效,将自动使用默认背景。请检查网络或图片路径。\\n - Q: 自定义API背景更新不及时?\\n A: 为保证速度,插件会预缓存下一张图片。若需立即更新,请重新进入设置页。\\n - Q: 兼容性问题?\\n A: 使用首页的“兼容性智能检测”功能获取诊断报告。\").textSize(13)\n .addTextView().text(\"7. 安全声明\").bold().marginTopDp(5).textColor(0xFFCC0000)\n .addTextView().text(\" - 本插件所有网络请求均直连第三方 API,不经过任何中转服务器。\\n - API Key 采用 Android KeyStore 系统级加密存储,极大降低泄露风险。\\n - 请勿在不可信的设备上使用本插件。\").textSize(12).textColor(0xFF888888)\n .build();\n new LiquidDialog(i).setTitle(\"全方位使用指南\").setView(v).setPositiveButton(\"已完全了解\", null).show();\n }\n\n void a(PluginUI i) {\n PluginView v = i.buildVerticalLayout()\n .addTextView().text(\"AI 文本分析助手 v1.0\").bold().textSize(18).paddingBottomDp(10)\n .addTextView().text(\"开源与声明:\").bold()\n .addTextView().text(\"本插件基于bin.mt.plugin api-3.0.0-alpha6构建,仅在MT管理器2.19.4进行测试,无法保证在其他版本正常运行。插件兼容性取决于 MT 管理器版本更新,如出现异常请及时反馈。本插件作为MT管理器扩展功能,不收集任何用户数据。所有API调用均直接发往用户配置的第三方服务商。用户需自行承担使用第三方 API 服务所产生的费用及风险。本插件不保证API服务的可用性及内容准确性。AI 生成的内容仅供参考,可能存在事实错误、偏见或不适当内容,请用户自行判断并承担使用责任。本插件不不对 AI 输出内容的准确性、完整性或合法性负责。请遵守相关法律法规使用。\\n\\n如需源码请联系作者获取。\\n\").textSize(13).paddingBottomDp(10)\n .addTextView().text(\"鸣谢:\").bold().addTextView().text(\"谨向 MT 管理器开发者 @Bin 及论坛众多插件开发者致以最崇高的敬意。本插件的开发是在借鉴论坛诸多优秀示例的基础上,并结合 Gemini3 pro 等 AI 工具的辅助,才得以基本实现设计目标。\").textSize(13).paddingBottomDp(10)\n .addTextView().text(\"免责声明\").bold().marginTopDp(10)\n .addTextView().text(\"本插件仅供学习交流使用,用户需自行承担使用第三方 API 服务所产生的费用及风险。AI 生成内容仅供参考。\").textSize(12).textColor(0xFF888888)\n .addTextView().text(\"联系作者\").bold().marginTopDp(10)\n .addTextView().text(\"QQ: 921992797\").textColor(0xFF336699).onClick(p -> c.openBrowser(\"https://ti.qq.com/open_qq/index2.html?url=mqqapi://userprofile/friend_profile_card?src_type=web&version=1.0&source=2&uin=921992797\"))\n .addTextView().text(\"论坛主页 (@sjlz)\").textColor(0xFF336699).onClick(p -> c.openBrowser(\"https://bbs.binmt.cc/home.php?mod=space&uid=153150&do=profile\"))\n .build();\n new LiquidDialog(i).setTitle(\"关于插件\").setView(v).setPositiveButton(\"这就去玩\", null).show();\n }\n\n void C(PluginUI i) {\n if (O.g(c).isEmpty()) { i.showToast(\"请先配置 API Key 后再使用智能检测\"); return; }\n i.showToast(\"正在启动深度兼容性扫描...\");\n \n new Thread(() -> {\n try {\n StringBuilder info = new StringBuilder();\n info.append(\"=== Device Diagnosis Report ===\\n\");\n info.append(\"Time: \").append(new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\").format(new Date())).append(\"\\n\");\n \n // 1. Basic Hardware\n info.append(\"\\n[Hardware]\\n\");\n info.append(\"Model: \").append(Build.MODEL).append(\"\\n\");\n info.append(\"Manufacturer: \").append(Build.MANUFACTURER).append(\"\\n\");\n info.append(\"Brand: \").append(Build.BRAND).append(\"\\n\");\n info.append(\"Board: \").append(Build.BOARD).append(\"\\n\");\n info.append(\"Device: \").append(Build.DEVICE).append(\"\\n\");\n info.append(\"Hardware: \").append(Build.HARDWARE).append(\"\\n\");\n info.append(\"Product: \").append(Build.PRODUCT).append(\"\\n\");\n try { \n if (Build.VERSION.SDK_INT >= 26) {\n // info.append(\"Serial: \").append(Build.getSerial()).append(\"\\n\"); // Requires permission\n }\n } catch (Exception e) { new O.L(c).e(\"Diag\", \"Serial check failed\", e); }\n\n // 2. CPU / ABI\n info.append(\"\\n[CPU & ABI]\\n\");\n info.append(\"Primary ABI: \").append(Build.CPU_ABI).append(\"\\n\");\n info.append(\"Secondary ABI: \").append(Build.CPU_ABI2).append(\"\\n\");\n info.append(\"Supported ABIs: \").append(Arrays.toString(Build.SUPPORTED_ABIS)).append(\"\\n\");\n \n int cores = Runtime.getRuntime().availableProcessors();\n info.append(\"Cores: \").append(cores).append(\"\\n\");\n \n try {\n BufferedReader br = new BufferedReader(new FileReader(\"/proc/cpuinfo\"));\n String line;\n while ((line = br.readLine()) != null) {\n if (line.startsWith(\"Hardware\")) info.append(\"CPU Hardware: \").append(line.split(\":\")[1].trim()).append(\"\\n\");\n if (line.startsWith(\"Processor\")) info.append(\"Processor: \").append(line.split(\":\")[1].trim()).append(\"\\n\");\n }\n br.close();\n } catch (Exception e) { new O.L(c).e(\"Diag\", \"CPU info read failed\", e); }\n \n // Iterate all cores for freq\n for (int k = 0; k < cores; k++) {\n try {\n String freq = readFileOneLine(\"/sys/devices/system/cpu/cpu\" + k + \"/cpufreq/scaling_cur_freq\");\n if (!freq.isEmpty()) info.append(\"Core \").append(k).append(\" Freq: \").append(Integer.parseInt(freq) / 1000).append(\" MHz\\n\");\n } catch (Exception e) { new O.L(c).e(\"Diag\", \"Core freq read failed\", e); }\n }\n try {\n String gov = readFileOneLine(\"/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor\");\n if (!gov.isEmpty()) info.append(\"Governor (Core0): \").append(gov).append(\"\\n\");\n } catch (Exception e) { new O.L(c).e(\"Diag\", \"Governor read failed\", e); }\n\n try {\n // Thermal\n File thermalDir = new File(\"/sys/class/thermal\");\n if (thermalDir.exists()) {\n File[] zones = thermalDir.listFiles((d, n) -> n.startsWith(\"thermal_zone\"));\n if (zones != null) {\n for (File z : zones) {\n String type = readFileOneLine(new File(z, \"type\").getAbsolutePath());\n String temp = readFileOneLine(new File(z, \"temp\").getAbsolutePath());\n if (!type.isEmpty() && !temp.isEmpty()) {\n try {\n float t = Integer.parseInt(temp) / 1000.0f;\n if (t > 0 && t < 100) info.append(\"Thermal \").append(type).append(\": \").append(String.format(\"%.1f\", t)).append(\"°C\\n\");\n } catch (Exception e) { new O.L(c).e(\"Diag\", \"Thermal parse failed\", e); }\n }\n }\n }\n }\n } catch (Exception e) { new O.L(c).e(\"Diag\", \"Thermal read failed\", e); }\n \n // 3. System\n info.append(\"\\n[System]\\n\");\n info.append(\"Android Ver: \").append(Build.VERSION.RELEASE).append(\" (SDK \").append(Build.VERSION.SDK_INT).append(\")\\n\");\n info.append(\"Security Patch: \").append(Build.VERSION.SECURITY_PATCH).append(\"\\n\");\n info.append(\"Fingerprint: \").append(Build.FINGERPRINT).append(\"\\n\");\n try { info.append(\"Kernel: \").append(System.getProperty(\"os.name\")).append(\" \").append(System.getProperty(\"os.version\")).append(\" \").append(System.getProperty(\"os.arch\")).append(\"\\n\"); } catch (Exception e) { new O.L(c).e(\"Diag\", \"Kernel info failed\", e); }\n try { info.append(\"Full Kernel: \").append(readFileOneLine(\"/proc/version\")).append(\"\\n\"); } catch (Exception e) { new O.L(c).e(\"Diag\", \"Full kernel read failed\", e); }\n\n // 4. Screen & Graphics\n info.append(\"\\n[Display & Graphics]\\n\");\n Context ctx = getGlobalContext();\n if (ctx != null) {\n android.util.DisplayMetrics dm = ctx.getResources().getDisplayMetrics();\n info.append(\"Resolution: \").append(dm.widthPixels).append(\"x\").append(dm.heightPixels).append(\"\\n\");\n info.append(\"Density: \").append(dm.densityDpi).append(\" dpi\\n\");\n \n WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);\n if (wm != null) {\n Display d = wm.getDefaultDisplay();\n info.append(\"Refresh Rate: \").append(d.getRefreshRate()).append(\" Hz\\n\");\n if (Build.VERSION.SDK_INT >= 24) {\n Display.HdrCapabilities hdr = d.getHdrCapabilities();\n if (hdr != null && hdr.getSupportedHdrTypes().length > 0) {\n info.append(\"HDR Support: Yes (Types: \").append(Arrays.toString(hdr.getSupportedHdrTypes())).append(\")\\n\");\n } else {\n info.append(\"HDR Support: No\\n\");\n }\n }\n }\n }\n \n // 5. Camera\n info.append(\"\\n[Camera]\\n\");\n if (ctx != null && Build.VERSION.SDK_INT >= 21) {\n try {\n android.hardware.camera2.CameraManager cm = (android.hardware.camera2.CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);\n String[] ids = cm.getCameraIdList();\n info.append(\"Cameras Found: \").append(ids.length).append(\"\\n\");\n for (String id : ids) {\n android.hardware.camera2.CameraCharacteristics cc = cm.getCameraCharacteristics(id);\n Integer facing = cc.get(android.hardware.camera2.CameraCharacteristics.LENS_FACING);\n String faceStr = (facing != null && facing == android.hardware.camera2.CameraCharacteristics.LENS_FACING_FRONT) ? \"Front\" : \"Back\";\n android.util.Size[] sizes = cc.get(android.hardware.camera2.CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(android.graphics.ImageFormat.JPEG);\n int mp = 0;\n if (sizes != null && sizes.length > 0) {\n mp = (sizes[0].getWidth() * sizes[0].getHeight()) / 1000000;\n }\n info.append(\"Cam \").append(id).append(\" (\").append(faceStr).append(\"): \").append(mp).append(\" MP\\n\");\n }\n } catch (Exception e) {\n info.append(\"Camera check failed: \").append(e.getMessage()).append(\"\\n\");\n new O.L(c).e(\"Diag\", \"Camera check failed\", e);\n }\n }\n\n // 6. Network\n info.append(\"\\n[Network]\\n\");\n if (ctx != null) {\n try {\n android.net.ConnectivityManager cm = (android.net.ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);\n if (Build.VERSION.SDK_INT >= 23) {\n android.net.Network net = cm.getActiveNetwork();\n if (net != null) {\n android.net.NetworkCapabilities nc = cm.getNetworkCapabilities(net);\n if (nc != null) {\n info.append(\"Transport: \");\n if (nc.hasTransport(android.net.NetworkCapabilities.TRANSPORT_WIFI)) info.append(\"WiFi \");\n if (nc.hasTransport(android.net.NetworkCapabilities.TRANSPORT_CELLULAR)) info.append(\"Cellular \");\n info.append(\"\\n\");\n info.append(\"Downstream: \").append(nc.getLinkDownstreamBandwidthKbps() / 1000).append(\" Mbps\\n\");\n }\n }\n }\n \n android.net.wifi.WifiManager wm = (android.net.wifi.WifiManager) ctx.getApplicationContext().getSystemService(Context.WIFI_SERVICE);\n if (wm != null && wm.isWifiEnabled()) {\n android.net.wifi.WifiInfo wi = wm.getConnectionInfo();\n if (wi != null) {\n info.append(\"WiFi SSID: \").append(wi.getSSID()).append(\"\\n\");\n info.append(\"WiFi Freq: \").append(wi.getFrequency()).append(\" MHz\\n\");\n info.append(\"WiFi Speed: \").append(wi.getLinkSpeed()).append(\" Mbps\\n\");\n }\n }\n \n // IP Address\n try {\n Enumeration en = NetworkInterface.getNetworkInterfaces();\n while (en.hasMoreElements()) {\n NetworkInterface intf = en.nextElement();\n Enumeration enumIpAddr = intf.getInetAddresses();\n while (enumIpAddr.hasMoreElements()) {\n InetAddress inetAddress = enumIpAddr.nextElement();\n if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {\n info.append(\"IP: \").append(inetAddress.getHostAddress()).append(\"\\n\");\n }\n }\n }\n } catch (Exception e) { new O.L(c).e(\"Diag\", \"IP check failed\", e); }\n } catch (Exception e) { info.append(\"Network check failed: \").append(e.getMessage()).append(\"\\n\"); new O.L(c).e(\"Diag\", \"Network info failed\", e); }\n }\n\n // 7. Battery & Power\n info.append(\"\\n[Battery & Power]\\n\");\n if (ctx != null) {\n Intent batteryStatus = ctx.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));\n if (batteryStatus != null) {\n int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);\n int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);\n int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);\n int health = batteryStatus.getIntExtra(BatteryManager.EXTRA_HEALTH, -1);\n int temp = batteryStatus.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);\n int voltage = batteryStatus.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);\n \n info.append(\"Level: \").append(level * 100 / (float)scale).append(\"%\\n\");\n info.append(\"Temp: \").append(temp / 10.0).append(\" °C\\n\");\n info.append(\"Voltage: \").append(voltage).append(\" mV\\n\");\n \n BatteryManager bm = (BatteryManager) ctx.getSystemService(Context.BATTERY_SERVICE);\n if (Build.VERSION.SDK_INT >= 21) {\n int currentNow = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW);\n int currentAvg = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE);\n info.append(\"Current (Now): \").append(currentNow / 1000).append(\" mA\\n\");\n info.append(\"Current (Avg): \").append(currentAvg / 1000).append(\" mA\\n\");\n long chargeCounter = bm.getLongProperty(BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER);\n info.append(\"Charge Counter: \").append(chargeCounter / 1000).append(\" mAh\\n\");\n \n // 尝试读取更多电池容量信息\n try {\n File psDir = new File(\"/sys/class/power_supply\");\n if (psDir.exists() && psDir.isDirectory()) {\n File[] files = psDir.listFiles();\n if (files != null) {\n long totalCap = 0;\n boolean foundAny = false;\n for (File f : files) {\n String name = f.getName().toLowerCase();\n if (name.contains(\"battery\") || name.contains(\"bms\")) {\n String[] checkFiles = {\"charge_full\", \"charge_full_design\", \"capacity_mah\"};\n for (String cf : checkFiles) {\n File target = new File(f, cf);\n if (target.exists()) {\n String val = readFileOneLine(target.getAbsolutePath());\n if (!val.isEmpty()) {\n try {\n long v = Long.parseLong(val.trim());\n if (v > 0) {\n String unit = \"uAh\";\n long mah = v / 1000;\n if (cf.contains(\"mah\")) {\n mah = v;\n unit = \"mAh\";\n } else if (v < 20000) { \n // If value is small, it might be mAh already or just wrong, but assume mAh if < 20000 (20Ah)\n // Typical phone battery: 3000-6000 mAh. \n // If uAh: 3000000 - 6000000.\n // So if > 100000, likely uAh.\n if (v < 100000) { mah = v; unit = \"mAh (guessed)\"; }\n }\n \n info.append(\"Capacity (\").append(name).append(\"/\").append(cf).append(\"): \").append(mah).append(\" mAh\\n\");\n \n // Simple heuristic for dual cell total:\n // If we see \"battery\" and \"battery2\" with similar large values, we might want to sum them mentally, \n // but here we just list them for the user to see.\n // Some kernels report total in \"battery\" and parts in \"bms\".\n }\n } catch (Exception e) {\n new O.L(c).e(\"Diag\", \"Battery parse failed\", e);\n }\n // If we found a valid value for this node, maybe skip other checks for this node to avoid duplicates? \n // But maybe charge_full and charge_full_design differ. Let's keep all.\n }\n }\n }\n }\n }\n }\n }\n } catch (Exception e) {\n new O.L(c).e(\"Diag\", \"Battery sysfs scan failed\", e);\n }\n }\n }\n }\n \n // 8. Memory & Storage\n info.append(\"\\n[Memory & Storage]\\n\");\n if (ctx != null) {\n android.app.ActivityManager am = (android.app.ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);\n android.app.ActivityManager.MemoryInfo mi = new android.app.ActivityManager.MemoryInfo();\n am.getMemoryInfo(mi);\n info.append(\"RAM Total: \").append(mi.totalMem / 1024 / 1024).append(\" MB\\n\");\n info.append(\"RAM Avail: \").append(mi.availMem / 1024 / 1024).append(\" MB\\n\");\n \n File dataDir = Environment.getDataDirectory();\n StatFs stat = new StatFs(dataDir.getPath());\n info.append(\"Internal Total: \").append(stat.getTotalBytes() / 1024 / 1024 / 1024).append(\" GB\\n\");\n info.append(\"Internal Avail: \").append(stat.getAvailableBytes() / 1024 / 1024 / 1024).append(\" GB\\n\");\n }\n \n // 9. Sensors\n if (ctx != null) {\n SensorManager sm = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE);\n if (sm != null) {\n info.append(\"\\n[Sensors]\\n\");\n java.util.List sensors = sm.getSensorList(Sensor.TYPE_ALL);\n for(Sensor s : sensors) info.append(s.getName()).append(\", \");\n info.append(\"\\n\");\n }\n }\n\n // 10. Environment Security\n info.append(\"\\n[Environment Security]\\n\");\n String[] suPaths = {\"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/system/sd/xbin/su\", \"/system/bin/failsafe/su\", \"/data/local/xbin/su\", \"/data/local/bin/su\", \"/system/usr/we-need-root/su\", \"/magisk/.core/bin/su\"};\n boolean isRooted = false;\n for (String p : suPaths) {\n if (new File(p).exists()) { isRooted = true; info.append(\"SU Binary: \").append(p).append(\"\\n\"); break; }\n }\n info.append(\"Root Access: \").append(isRooted ? \"Yes\" : \"No (File Check)\").append(\"\\n\");\n\n try (java.util.Scanner s = new java.util.Scanner(new File(\"/proc/mounts\"))) {\n boolean magiskFound = false;\n while (s.hasNextLine()) {\n String line = s.nextLine();\n if (line.contains(\"magisk\") || line.contains(\"core/mirror\") || line.contains(\"com.topjohnwu.magisk\")) { magiskFound = true; break; }\n }\n info.append(\"Magisk Mount: \").append(magiskFound).append(\"\\n\");\n } catch (Exception ignored) { new O.L(c).e(\"Diag\", \"Magisk check failed\", ignored); }\n \n // System Properties Reflection\n info.append(\"\\n[System Properties]\\n\");\n try {\n Class sysProp = Class.forName(\"android.os.SystemProperties\");\n Method get = sysProp.getMethod(\"get\", String.class);\n String[] keys = {\n \"ro.build.type\", \"ro.build.tags\", \"ro.build.flavor\", \"ro.build.display.id\",\n \"ro.product.cpu.abi\", \"ro.board.platform\", \"ro.hardware\", \n \"ro.secure\", \"ro.debuggable\", \"ro.boot.flash.locked\", \"ro.boot.verifiedbootstate\"\n };\n for (String k : keys) {\n String v = (String) get.invoke(null, k);\n if (v != null && !v.isEmpty()) info.append(k).append(\": \").append(v).append(\"\\n\");\n }\n } catch (Exception ignored) { new O.L(c).e(\"Diag\", \"SysProp check failed\", ignored); }\n\n // Host App Info\n info.append(\"\\n[Host App]\\n\");\n try {\n if (ctx != null) {\n android.content.pm.PackageInfo pi = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0);\n info.append(\"Package: \").append(pi.packageName).append(\"\\n\");\n info.append(\"Version Name: \").append(pi.versionName).append(\"\\n\");\n info.append(\"Version Code: \").append(pi.versionCode).append(\"\\n\");\n info.append(\"Installer: \").append(ctx.getPackageManager().getInstallerPackageName(ctx.getPackageName())).append(\"\\n\");\n info.append(\"Last Update: \").append(new Date(pi.lastUpdateTime)).append(\"\\n\");\n }\n } catch (Exception e) {\n info.append(\"Host info failed: \").append(e.getMessage()).append(\"\\n\");\n new O.L(c).e(\"Diag\", \"Host info failed\", e);\n }\n\n // Plugin Self Info\n info.append(\"\\n[Plugin Self]\\n\");\n info.append(\"Name: AI文本分析助手\\n\");\n info.append(\"Version: \").append(c.getPluginVersionName()).append(\" (\").append(c.getPluginVersionCode()).append(\")\\n\");\n info.append(\"SDK Version: \").append(PluginContext.SDK_VERSION).append(\"\\n\");\n info.append(\"Min API: 26 (Android 8.0)\\n\");\n info.append(\"开发者仅在MT管理器2.19.4及安卓15版本测试正常运行,其他版本请用户自行测试,理论上支持所有安卓8.0及以上版本。\\n\");\n\n // Prompt Construction\n StringBuilder prompt = new StringBuilder();\n prompt.append(\"你是一位资深的 Android 系统与应用兼容性专家,同时也是 MT 管理器插件开发的权威顾问。请根据以下收集到的详细设备环境信息,对“AI文本分析助手”插件的运行兼容性进行全方位的深度诊断。\\n\\n\");\n prompt.append(\"【环境信息快照】\\n\").append(info.toString()).append(\"\\n\\n\");\n prompt.append(\"【插件技术规格】\\n\");\n prompt.append(\"- 核心架构:WebView 混合渲染 (Require API 26+)\\n\");\n prompt.append(\"- 关键依赖:OkHttp 4 (Require API 21+), SQLite, Android KeyStore\\n\");\n prompt.append(\"- 渲染特性:CSS3 Flexbox, Markdown Parsing, Hardware Acceleration\\n\");\n prompt.append(\"- 安全特性:AES-GCM Encryption, Reflection-based UI Injection\\n\\n\");\n prompt.append(\"【诊断报告要求】\\n\");\n prompt.append(\"请以亲切、专业且易于理解的中文输出报告,包含以下部分:\\n\");\n prompt.append(\"1. **综合评分** (0-100分,并给出评级,如 S/A/B/C)\\n\");\n prompt.append(\"2. **环境亮点** (如高性能CPU、大内存、新版系统等)\\n\");\n prompt.append(\"3. **潜在风险** (重点分析 Root/Magisk 对 WebView 的影响、系统版本过旧、Webview 版本过低等风险)\\n\");\n prompt.append(\"4. **优化建议** (针对当前环境的个性化设置建议)\\n\");\n prompt.append(\"5. **最终结论** (是否推荐在此设备上长期使用)\\n\");\n prompt.append(\"6. **详细硬件分析** (针对存储、电池、传感器等新增检测项进行详细点评)\\n\");\n\n new Handler(Looper.getMainLooper()).post(() -> {\n if (o != null) o.a(i, prompt.toString(), \"\", \"Diagnose_\" + System.currentTimeMillis());\n });\n\n } catch (Exception e) {\n new O.L(c).e(\"Diag\", \"Global diagnosis failed\", e);\n new Handler(Looper.getMainLooper()).post(() -> i.showToast(\"诊断过程发生错误: \" + e.getMessage()));\n }\n }).start();\n }\n \n private String readFileOneLine(String path) {\n try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n return br.readLine();\n } catch (Exception e) {\n // Suppress log for /proc/version permission denied as it is expected on Android 11+\n // Also suppress permission denied for power_supply on some devices\n if (!path.equals(\"/proc/version\") && !e.getMessage().contains(\"EACCES\") && !e.getMessage().contains(\"Permission denied\")) {\n new O.L(c).e(\"IO\", \"Read file failed: \" + path, e);\n }\n return \"\";\n }\n }\n\n // Helper to get Context safely\n private Context getGlobalContext() {\n try {\n Class atClass = Class.forName(\"android.app.ActivityThread\");\n return (Context) atClass.getMethod(\"currentApplication\").invoke(null);\n } catch (Exception e) {\n if (c != null) new O.L(c).e(\"UI\", \"Global context lookup failed\", e);\n return null;\n }\n }\n}\n"}}}}