v3.1.1: fix download mirrors, Hermes auto-install Python, AutoGLM Samsung/Android 16 fix
This commit is contained in:
@@ -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 21
|
||||||
versionName "3.1.0"
|
versionName "3.1.1"
|
||||||
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:!*~'
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zai-chat",
|
"name": "zai-chat",
|
||||||
"version": "3.1.0",
|
"version": "3.1.1",
|
||||||
"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": {
|
||||||
|
|||||||
BIN
releases/Z.AI-Chat-v3.1.1-release.apk.idsig
Normal file
BIN
releases/Z.AI-Chat-v3.1.1-release.apk.idsig
Normal file
Binary file not shown.
@@ -2051,17 +2051,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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user