Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/bilieebiliee1-design_ai-text-assistant-plugin.json

1 line
118 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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<PluginContext> weakContext;\n\n // 预定义的 API 服务商列表 (保持不变)\n static java.util.List<p> 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<Bitmap> 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 = \"<html><body style='margin:0;padding:0;width:100%;height:100%;\" +\n \"background-image:url(\" + imgData + \");\" +\n \"background-position:center center;\" +\n \"background-repeat:no-repeat;\" +\n \"background-size:cover;'>\" + // 或者 cover,视需求而定\n \"</body></html>\";\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<View> 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<String, BtnConfig> buttons = new java.util.HashMap<>();\n private java.util.List<String> 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<currentOrder.size(); i++) {\n sb.append(currentOrder.get(i));\n if(i < currentOrder.size()-1) sb.append(\",\");\n }\n pluginContext.getPreferences().edit().putString(\"dialog_btn_order\", sb.toString()).apply();\n }\n renderButtons();\n }\n }\n }\n\n // =========================================================\n // 原有逻辑函数 (保留完整并增强背景设置)\n // =========================================================\n\n void B(PluginUI i) {\n SharedPreferences s = c.getPreferences();\n // 使用数组容器解决变量未初始化即在回调中引用的编译报错\n final PluginView[] vArr = {null};\n\n PluginRootLayoutBuilder builder = i.buildVerticalLayout();\n \n builder.addTextView().text(\"背景图片来源:\").bold()\n .addSpinner(\"mode\").items(Arrays.asList(\"程序内置\", \"指定本地图片\", \"自定义API\", \"本地缓存库\")).selection(s.getInt(\"bg_mode\", 0))\n \n // --- 本地路径部分 ---\n .addTextView(\"h_local\").text(\"本地图片路径:\").bold().marginTopDp(10)\n .addEditText(\"local_path\").text(s.getString(\"bg_local_path\", \"\")).hint(\"请输入图片的绝对路径\").singleLine(true)\n \n // --- API URL 部分 ---\n .addTextView(\"h_api\").text(\"API URL:\").bold().marginTopDp(10)\n .addEditText(\"api_url\").text(s.getString(\"bg_api_url\", \"https://yingciyuan.cn/pe.php\")).hint(\"请输入 API 请求地址\").singleLine(true)\n \n // --- API 缓存控制部分 ---\n .addTextView(\"h_cache\").text(\"API 自动缓存控制:\").bold().marginTopDp(10)\n .addButton(\"cache\").text(\"从当前 API 缓存 5 张图片到本地库\").onClick(btn -> {\n // 通过 vArr[0] 安全引用已构建的视图\n if (vArr[0] != null) {\n String currentApi = vArr[0].<PluginEditText>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].<PluginEditText>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.<PluginEditText>requireViewById(\"local_path\").getText().toString().trim();\n String apiU = v.<PluginEditText>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.<PluginEditText>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.<PluginEditText>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.<PluginEditText>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; k<colorKeys.length; k++) {\n String hex = v.<PluginEditText>requireViewById(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<String> 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<String> 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.<PluginEditText>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.<PluginEditText>requireViewById(\"t\").getText().toString(), TP = v.<PluginEditText>requireViewById(\"p\").getText().toString(), MT = v.<PluginEditText>requireViewById(\"m\").getText().toString(), EX = v.<PluginEditText>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<String> 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.<PluginEditText>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<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();\n while (en.hasMoreElements()) {\n NetworkInterface intf = en.nextElement();\n Enumeration<InetAddress> 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<Sensor> 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"}}}}