diff --git a/android/app/build.gradle b/android/app/build.gradle index 2d0ff6a..f7f9469 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "ai.z.chat" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 20 - versionName "3.1.0" + versionCode 21 + versionName "3.1.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ef879aa..11a7b4e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,7 +8,6 @@ - + android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> diff --git a/android/app/src/main/java/ai/z/chat/AutoGLMPlugin.java b/android/app/src/main/java/ai/z/chat/AutoGLMPlugin.java index 2f7f12f..638dd25 100644 --- a/android/app/src/main/java/ai/z/chat/AutoGLMPlugin.java +++ b/android/app/src/main/java/ai/z/chat/AutoGLMPlugin.java @@ -27,19 +27,10 @@ public class AutoGLMPlugin extends Plugin { try { Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); 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); call.resolve(new JSObject().put("opened", true)); } catch (Exception e) { - try { - 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()); - } + call.reject("Cannot open accessibility settings: " + e.getMessage()); } } diff --git a/android/app/src/main/java/ai/z/chat/AutoGLMService.java b/android/app/src/main/java/ai/z/chat/AutoGLMService.java index c209759..75377d7 100644 --- a/android/app/src/main/java/ai/z/chat/AutoGLMService.java +++ b/android/app/src/main/java/ai/z/chat/AutoGLMService.java @@ -16,8 +16,6 @@ import android.view.accessibility.AccessibilityWindowInfo; import org.json.JSONArray; import org.json.JSONObject; -import android.os.Bundle; - import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; diff --git a/android/app/src/main/java/ai/z/chat/BootstrapPlugin.java b/android/app/src/main/java/ai/z/chat/BootstrapPlugin.java index 6ff6ea8..385c901 100644 --- a/android/app/src/main/java/ai/z/chat/BootstrapPlugin.java +++ b/android/app/src/main/java/ai/z/chat/BootstrapPlugin.java @@ -42,6 +42,15 @@ public class BootstrapPlugin extends Plugin { 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"; + 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 int BUFFER_SIZE = 8192; @@ -108,16 +117,28 @@ public class BootstrapPlugin extends Plugin { private void doInstall(PluginCall call) throws Exception { String arch = getArch(); - String bootstrapUrl = getBootstrapUrl(arch); + String[] urls = getBootstrapUrls(arch); sendProgress(call, "Downloading bootstrap for " + arch + "...", 0); File zipFile = new File(getContext().getCacheDir(), "bootstrap.zip"); - downloadFile(bootstrapUrl, 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); - }); + Exception lastError = null; + for (String url : urls) { + try { + 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); @@ -570,52 +591,84 @@ public class BootstrapPlugin extends Plugin { 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"; if (new File(hermesLink).exists()) { call.resolve(new JSObject().put("installed", true).put("path", hermesLink).put("venv", venvDir)); return; } - Log.i(TAG, "Creating Hermes Python venv..."); - ProcessBuilder pb = new ProcessBuilder(pythonBin, "-m", "venv", venvDir); - 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 pythonBin = prefix + "/bin/python3"; + String pythonBinAlt = prefix + "/bin/python"; - String pipBin = venvDir + "/bin/pip"; - if (!new File(pipBin).exists()) { - call.reject("Failed to create Python venv"); + if (!new File(pythonBin).exists() && !new File(pythonBinAlt).exists()) { + Log.i(TAG, "Python not found, installing via pkg..."); + 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; } - 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.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.redirectErrorStream(true); p = pb.start(); 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.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); p = pb.start(); - drainProcess(p); - p.waitFor(); + String pipOutput = drainProcess(p); + boolean pipOk = p.waitFor(600, java.util.concurrent.TimeUnit.SECONDS); 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; } @@ -693,16 +746,20 @@ public class BootstrapPlugin extends Plugin { } } - private String getBootstrapUrl(String arch) { + private String[] getBootstrapUrls(String arch) { switch (arch) { - case "aarch64": return BOOTSTRAP_URL_AARCH64; - case "arm": return BOOTSTRAP_URL_ARM; - case "x86_64": return BOOTSTRAP_URL_X86_64; - case "i686": return BOOTSTRAP_URL_X86; - default: return BOOTSTRAP_URL_AARCH64; + case "aarch64": return new String[]{BOOTSTRAP_URL_AARCH64, BOOTSTRAP_MIRROR_AARCH64}; + case "arm": return new String[]{BOOTSTRAP_URL_ARM, BOOTSTRAP_MIRROR_ARM}; + case "x86_64": return new String[]{BOOTSTRAP_URL_X86_64, BOOTSTRAP_MIRROR_X86_64}; + case "i686": return new String[]{BOOTSTRAP_URL_X86, BOOTSTRAP_MIRROR_X86}; + 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) { if (file.isDirectory()) { File[] children = file.listFiles(); diff --git a/android/app/src/main/res/xml/accessibility_service_config.xml b/android/app/src/main/res/xml/accessibility_service_config.xml index 95ecfad..82dbcf6 100644 --- a/android/app/src/main/res/xml/accessibility_service_config.xml +++ b/android/app/src/main/res/xml/accessibility_service_config.xml @@ -5,6 +5,8 @@ android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagReportViewIds|flagIncludeNotImportantViews" android:canPerformGestures="true" android:canRetrieveWindowContent="true" + android:canTakeScreenshot="true" + android:label="@string/app_name" android:notificationTimeout="100" android:description="@string/accessibility_service_desc" android:settingsActivity="ai.z.chat.MainActivity" diff --git a/package.json b/package.json index 731893d..efcec97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zai-chat", - "version": "3.1.0", + "version": "3.1.1", "description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan", "main": "index.js", "scripts": { diff --git a/releases/Z.AI-Chat-v3.1.1-release.apk.idsig b/releases/Z.AI-Chat-v3.1.1-release.apk.idsig new file mode 100644 index 0000000..8b5ab1b Binary files /dev/null and b/releases/Z.AI-Chat-v3.1.1-release.apk.idsig differ diff --git a/www/js/app.js b/www/js/app.js index 6ac9db1..3b62770 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -2051,17 +2051,19 @@ var d8Test = await shellExec('test -f "' + jarsDir + '/d8.jar"', termState.homeDir, false); if (d8Test.exitCode !== 0) { - termPrint('[*] Downloading d8.jar (DEX compiler, ~18MB)...', 'info'); + termPrint('[*] Extracting d8.jar from APK assets...', 'info'); try { - var dlResult = await Bootstrap.downloadFile({url: 'https://dl.google.com/android/repository/build-tools_r36-linux.zip', dest: toolsDir + '/build-tools.zip'}); - termPrint('[*] Extracting d8.jar from build-tools...', 'info'); - 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); + var d8Extract = await Bootstrap.extractAsset({src: 'jars/d8.jar', dest: jarsDir + '/d8.jar'}); + termPrint('[OK] d8.jar extracted (' + Math.round(d8Extract.size/1024/1024) + ' MB)', 'success'); } catch(e) { - termPrint('[!] d8.jar download failed: ' + e.message, 'warning'); - termPrint('[*] Trying Termux dx package...', 'info'); + termPrint('[!] d8.jar extract failed: ' + e.message, 'warning'); + termPrint('[*] Downloading d8.jar (~18MB)...', 'info'); try { - var dxResult = await Bootstrap.installProot(); - } catch(e2) {} + await Bootstrap.downloadFile({url: 'https://dl.google.com/android/repository/build-tools_r36-linux.zip', dest: toolsDir + '/build-tools.zip'}); + 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'); + } } }