2 Commits

10 changed files with 109 additions and 60 deletions

View File

@@ -7,8 +7,8 @@ android {
applicationId "ai.z.chat" applicationId "ai.z.chat"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 20 versionCode 22
versionName "3.1.0" versionName "3.1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions { aaptOptions {
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'

View File

@@ -8,7 +8,6 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -41,8 +40,7 @@
<service <service
android:name=".AutoGLMService" android:name=".AutoGLMService"
android:exported="false" android:exported="false"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
android:foregroundServiceType="mediaPlayback">
<intent-filter> <intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" /> <action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter> </intent-filter>

View File

@@ -27,19 +27,10 @@ public class AutoGLMPlugin extends Plugin {
try { try {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String label = getContext().getString(android.R.string.ok);
intent.putExtra(":settings:fragment_args_key", "ai.z.chat/.AutoGLMService");
getContext().startActivity(intent); getContext().startActivity(intent);
call.resolve(new JSObject().put("opened", true)); call.resolve(new JSObject().put("opened", true));
} catch (Exception e) { } catch (Exception e) {
try { call.reject("Cannot open accessibility settings: " + e.getMessage());
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
call.resolve(new JSObject().put("opened", true));
} catch (Exception e2) {
call.reject("Cannot open accessibility settings: " + e2.getMessage());
}
} }
} }

View File

@@ -16,8 +16,6 @@ import android.view.accessibility.AccessibilityWindowInfo;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import android.os.Bundle;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@@ -42,6 +42,15 @@ public class BootstrapPlugin extends Plugin {
private static final String BOOTSTRAP_URL_X86 = private static final String BOOTSTRAP_URL_X86 =
"https://github.com/termux/termux-packages/releases/download/bootstrap-2026.02.12-r1%2Bapt.android-7/bootstrap-i686.zip"; "https://github.com/termux/termux-packages/releases/download/bootstrap-2026.02.12-r1%2Bapt.android-7/bootstrap-i686.zip";
private static final String BOOTSTRAP_MIRROR_AARCH64 =
"https://mirror.termux.dev/termux/termux-packages/bootstrap-2026.02.12-r1%2Bapt.android-7/bootstrap-aarch64.zip";
private static final String BOOTSTRAP_MIRROR_ARM =
"https://mirror.termux.dev/termux/termux-packages/bootstrap-2026.02.12-r1%2Bapt.android-7/bootstrap-arm.zip";
private static final String BOOTSTRAP_MIRROR_X86_64 =
"https://mirror.termux.dev/termux/termux-packages/bootstrap-2026.02.12-r1%2Bapt.android-7/bootstrap-x86_64.zip";
private static final String BOOTSTRAP_MIRROR_X86 =
"https://mirror.termux.dev/termux/termux-packages/bootstrap-2026.02.12-r1%2Bapt.android-7/bootstrap-i686.zip";
private static final String TERMUX_PREFIX = "/data/data/com.termux/files/usr"; private static final String TERMUX_PREFIX = "/data/data/com.termux/files/usr";
private static final int BUFFER_SIZE = 8192; private static final int BUFFER_SIZE = 8192;
@@ -108,16 +117,28 @@ public class BootstrapPlugin extends Plugin {
private void doInstall(PluginCall call) throws Exception { private void doInstall(PluginCall call) throws Exception {
String arch = getArch(); String arch = getArch();
String bootstrapUrl = getBootstrapUrl(arch); String[] urls = getBootstrapUrls(arch);
sendProgress(call, "Downloading bootstrap for " + arch + "...", 0); sendProgress(call, "Downloading bootstrap for " + arch + "...", 0);
File zipFile = new File(getContext().getCacheDir(), "bootstrap.zip"); File zipFile = new File(getContext().getCacheDir(), "bootstrap.zip");
downloadFile(bootstrapUrl, zipFile, (downloaded, total) -> { Exception lastError = null;
int percent = total > 0 ? (int)(downloaded * 100 / total) : 0; for (String url : urls) {
String sizeMB = String.format("%.1f", downloaded / (1024.0 * 1024.0)); try {
sendProgress(call, "Downloading... " + sizeMB + " MB (" + percent + "%)", percent / 3); downloadFile(url, zipFile, (downloaded, total) -> {
}); int percent = total > 0 ? (int)(downloaded * 100 / total) : 0;
String sizeMB = String.format("%.1f", downloaded / (1024.0 * 1024.0));
sendProgress(call, "Downloading... " + sizeMB + " MB (" + percent + "%)", percent / 3);
});
lastError = null;
break;
} catch (Exception e) {
lastError = e;
Log.w(TAG, "Download from " + url + " failed, trying next mirror...");
sendProgress(call, "Retrying alternate source...", 5);
}
}
if (lastError != null) throw lastError;
sendProgress(call, "Extracting bootstrap...", 35); sendProgress(call, "Extracting bootstrap...", 35);
@@ -570,52 +591,84 @@ public class BootstrapPlugin extends Plugin {
new File(hermesDir).mkdirs(); new File(hermesDir).mkdirs();
String pythonBin = prefix + "/bin/python";
if (!new File(pythonBin).exists()) {
call.reject("Python not installed. Run Termux bootstrap first.");
return;
}
String hermesLink = venvDir + "/bin/hermes"; String hermesLink = venvDir + "/bin/hermes";
if (new File(hermesLink).exists()) { if (new File(hermesLink).exists()) {
call.resolve(new JSObject().put("installed", true).put("path", hermesLink).put("venv", venvDir)); call.resolve(new JSObject().put("installed", true).put("path", hermesLink).put("venv", venvDir));
return; return;
} }
Log.i(TAG, "Creating Hermes Python venv..."); String pythonBin = prefix + "/bin/python3";
ProcessBuilder pb = new ProcessBuilder(pythonBin, "-m", "venv", venvDir); String pythonBinAlt = prefix + "/bin/python";
pb.environment().put("ANDROID_API_LEVEL", String.valueOf(android.os.Build.VERSION.SDK_INT));
pb.environment().put("HOME", home);
pb.redirectErrorStream(true);
Process p = pb.start();
drainProcess(p);
p.waitFor();
String pipBin = venvDir + "/bin/pip"; if (!new File(pythonBin).exists() && !new File(pythonBinAlt).exists()) {
if (!new File(pipBin).exists()) { Log.i(TAG, "Python not found, installing via pkg...");
call.reject("Failed to create Python venv"); String pkgBin = prefix + "/bin/pkg";
String aptBin = prefix + "/bin/apt";
String installBin = new File(pkgBin).exists() ? pkgBin : aptBin;
if (!new File(installBin).exists()) {
call.reject("Bootstrap not installed. Install dev tools first.");
return;
}
ProcessBuilder pb = new ProcessBuilder("/system/bin/sh", "-c",
"export PREFIX=\"" + prefix + "\" LD_LIBRARY_PATH=\"" + prefix + "/lib\" PATH=\"" + prefix + "/bin:/system/bin:$PATH\" HOME=\"" + home + "\" ANDROID_API_LEVEL=\"" + android.os.Build.VERSION.SDK_INT + "\" && " +
"sh \"" + installBin + "\" install -y python clang rust make pkg-config libffi openssl 2>&1");
pb.redirectErrorStream(true);
Process p = pb.start();
String installOutput = drainProcess(p);
p.waitFor(300, java.util.concurrent.TimeUnit.SECONDS);
Log.i(TAG, "Python install output: " + installOutput.substring(0, Math.min(500, installOutput.length())));
}
if (!new File(pythonBin).exists() && !new File(pythonBinAlt).exists()) {
call.reject("Python installation failed. Try: pkg install python");
return; return;
} }
Log.i(TAG, "Installing hermes-agent..."); String usePython = new File(pythonBin).exists() ? pythonBin : pythonBinAlt;
Log.i(TAG, "Creating Hermes Python venv...");
ProcessBuilder pb = new ProcessBuilder(usePython, "-m", "venv", venvDir);
pb.environment().put("ANDROID_API_LEVEL", String.valueOf(android.os.Build.VERSION.SDK_INT));
pb.environment().put("HOME", home);
pb.environment().put("LD_LIBRARY_PATH", prefix + "/lib");
pb.environment().put("PREFIX", prefix);
pb.redirectErrorStream(true);
Process p = pb.start();
String venvOutput = drainProcess(p);
p.waitFor(60, java.util.concurrent.TimeUnit.SECONDS);
Log.i(TAG, "venv output: " + venvOutput.substring(0, Math.min(300, venvOutput.length())));
String pipBin = venvDir + "/bin/pip";
if (!new File(pipBin).exists()) {
call.reject("Failed to create Python venv: " + venvOutput.substring(0, Math.min(200, venvOutput.length())));
return;
}
Log.i(TAG, "Upgrading pip...");
pb = new ProcessBuilder(pipBin, "install", "--upgrade", "pip", "setuptools", "wheel"); pb = new ProcessBuilder(pipBin, "install", "--upgrade", "pip", "setuptools", "wheel");
pb.environment().put("ANDROID_API_LEVEL", String.valueOf(android.os.Build.VERSION.SDK_INT)); pb.environment().put("ANDROID_API_LEVEL", String.valueOf(android.os.Build.VERSION.SDK_INT));
pb.environment().put("HOME", home); pb.environment().put("HOME", home);
pb.environment().put("LD_LIBRARY_PATH", prefix + "/lib");
pb.redirectErrorStream(true); pb.redirectErrorStream(true);
p = pb.start(); p = pb.start();
drainProcess(p); drainProcess(p);
p.waitFor(); p.waitFor(120, java.util.concurrent.TimeUnit.SECONDS);
Log.i(TAG, "Installing hermes-agent...");
pb = new ProcessBuilder(pipBin, "install", "hermes-agent"); pb = new ProcessBuilder(pipBin, "install", "hermes-agent");
pb.environment().put("ANDROID_API_LEVEL", String.valueOf(android.os.Build.VERSION.SDK_INT)); pb.environment().put("ANDROID_API_LEVEL", String.valueOf(android.os.Build.VERSION.SDK_INT));
pb.environment().put("HOME", home); pb.environment().put("HOME", home);
pb.environment().put("LD_LIBRARY_PATH", prefix + "/lib");
pb.environment().put("PREFIX", prefix);
pb.redirectErrorStream(true); pb.redirectErrorStream(true);
p = pb.start(); p = pb.start();
drainProcess(p); String pipOutput = drainProcess(p);
p.waitFor(); boolean pipOk = p.waitFor(600, java.util.concurrent.TimeUnit.SECONDS);
if (!new File(venvDir + "/bin/hermes").exists()) { if (!new File(venvDir + "/bin/hermes").exists()) {
call.reject("hermes-agent installation failed"); call.reject("hermes-agent installation failed: " + pipOutput.substring(0, Math.min(500, pipOutput.length())));
return; return;
} }
@@ -693,16 +746,20 @@ public class BootstrapPlugin extends Plugin {
} }
} }
private String getBootstrapUrl(String arch) { private String[] getBootstrapUrls(String arch) {
switch (arch) { switch (arch) {
case "aarch64": return BOOTSTRAP_URL_AARCH64; case "aarch64": return new String[]{BOOTSTRAP_URL_AARCH64, BOOTSTRAP_MIRROR_AARCH64};
case "arm": return BOOTSTRAP_URL_ARM; case "arm": return new String[]{BOOTSTRAP_URL_ARM, BOOTSTRAP_MIRROR_ARM};
case "x86_64": return BOOTSTRAP_URL_X86_64; case "x86_64": return new String[]{BOOTSTRAP_URL_X86_64, BOOTSTRAP_MIRROR_X86_64};
case "i686": return BOOTSTRAP_URL_X86; case "i686": return new String[]{BOOTSTRAP_URL_X86, BOOTSTRAP_MIRROR_X86};
default: return BOOTSTRAP_URL_AARCH64; default: return new String[]{BOOTSTRAP_URL_AARCH64, BOOTSTRAP_MIRROR_AARCH64};
} }
} }
private String getMirrorUrl(String primaryUrl) {
return primaryUrl.replace("https://github.com/termux/", "https://mirror.termux.dev/termux/");
}
private void deleteRecursive(File file) { private void deleteRecursive(File file) {
if (file.isDirectory()) { if (file.isDirectory()) {
File[] children = file.listFiles(); File[] children = file.listFiles();

View File

@@ -5,6 +5,8 @@
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagReportViewIds|flagIncludeNotImportantViews" android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagReportViewIds|flagIncludeNotImportantViews"
android:canPerformGestures="true" android:canPerformGestures="true"
android:canRetrieveWindowContent="true" android:canRetrieveWindowContent="true"
android:canTakeScreenshot="true"
android:label="@string/app_name"
android:notificationTimeout="100" android:notificationTimeout="100"
android:description="@string/accessibility_service_desc" android:description="@string/accessibility_service_desc"
android:settingsActivity="ai.z.chat.MainActivity" android:settingsActivity="ai.z.chat.MainActivity"

View File

@@ -1,6 +1,6 @@
{ {
"name": "zai-chat", "name": "zai-chat",
"version": "3.1.0", "version": "3.1.2",
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan", "description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

Binary file not shown.

Binary file not shown.

View File

@@ -1441,6 +1441,7 @@
var deviceCurrentAppRegex = /\[DEVICE_CURRENT_APP\]/gi; var deviceCurrentAppRegex = /\[DEVICE_CURRENT_APP\]/gi;
var hermesInstallRegex = /\[HERMES_INSTALL\]/gi; var hermesInstallRegex = /\[HERMES_INSTALL\]/gi;
var hermesExecRegex = /\[HERMES_EXEC\s+([^\]]+)\]/gi; var hermesExecRegex = /\[HERMES_EXEC\s+([^\]]+)\]/gi;
var codeBlockFileRegex = /```(\w+)\s*\n([\s\S]*?)```/gi;
var match; var match;
while ((match = createActionRegex.exec(content)) !== null) { while ((match = createActionRegex.exec(content)) !== null) {
@@ -2051,17 +2052,19 @@
var d8Test = await shellExec('test -f "' + jarsDir + '/d8.jar"', termState.homeDir, false); var d8Test = await shellExec('test -f "' + jarsDir + '/d8.jar"', termState.homeDir, false);
if (d8Test.exitCode !== 0) { if (d8Test.exitCode !== 0) {
termPrint('[*] Downloading d8.jar (DEX compiler, ~18MB)...', 'info'); termPrint('[*] Extracting d8.jar from APK assets...', 'info');
try { try {
var dlResult = await Bootstrap.downloadFile({url: 'https://dl.google.com/android/repository/build-tools_r36-linux.zip', dest: toolsDir + '/build-tools.zip'}); var d8Extract = await Bootstrap.extractAsset({src: 'jars/d8.jar', dest: jarsDir + '/d8.jar'});
termPrint('[*] Extracting d8.jar from build-tools...', 'info'); termPrint('[OK] d8.jar extracted (' + Math.round(d8Extract.size/1024/1024) + ' MB)', 'success');
await shellExec('cd "' + toolsDir + '" && unzip -o build-tools.zip "*/lib/d8.jar" 2>&1 && mv */lib/d8.jar jars/d8.jar && rm -rf build-tools.zip android-*', termState.homeDir, false);
} catch(e) { } catch(e) {
termPrint('[!] d8.jar download failed: ' + e.message, 'warning'); termPrint('[!] d8.jar extract failed: ' + e.message, 'warning');
termPrint('[*] Trying Termux dx package...', 'info'); termPrint('[*] Downloading d8.jar (~18MB)...', 'info');
try { try {
var dxResult = await Bootstrap.installProot(); await Bootstrap.downloadFile({url: 'https://dl.google.com/android/repository/build-tools_r36-linux.zip', dest: toolsDir + '/build-tools.zip'});
} catch(e2) {} await shellExec('cd "' + toolsDir + '" && unzip -o build-tools.zip "*/lib/d8.jar" 2>&1 && mv */lib/d8.jar jars/d8.jar && rm -rf build-tools.zip android-*', termState.homeDir, false);
} catch(e2) {
termPrint('[!] d8.jar download also failed: ' + e2.message, 'err');
}
} }
} }