package br.carinha.rosto; import android.content.Context; import android.util.Log; import java.io.*; import java.net.*; import java.security.MessageDigest; /** Executor nativo no telefone. Preparação fora da thread da interface. */ public final class CerebroLocal { private volatile boolean fechado; private Process processo; public void iniciar(final Context contexto, final String servidor) { new Thread(() -> { try { File libs = new File(contexto.getApplicationInfo().nativeLibraryDir); File executavel = new File(libs, "libcarinha_ai.so"); if (!executavel.exists()) { Log.w("CarinhaIA", "Runtime ARM64 indisponivel"); return; } File modelo = new File(contexto.getFilesDir(), "qwen3-q4.gguf"); if (!modelo.exists()) { Log.i("CarinhaIA", "Baixando modelo do servidor local"); HttpURLConnection con = (HttpURLConnection)new URL(servidor.replaceAll("/+$", "") + "/ia/modelo").openConnection(); con.setConnectTimeout(10000); con.setReadTimeout(60000); File temporario = new File(contexto.getFilesDir(), "qwen3-q4.partial"); MessageDigest hash = MessageDigest.getInstance("SHA-256"); try (InputStream input = con.getInputStream(); FileOutputStream output = new FileOutputStream(temporario)) { byte[] buffer = new byte[65536]; int n; while ((n = input.read(buffer)) != -1) { if (fechado) return; output.write(buffer, 0, n); hash.update(buffer, 0, n); } } finally { con.disconnect(); } StringBuilder digest = new StringBuilder(); for (byte b : hash.digest()) digest.append(String.format("%02x", b & 255)); if (!digest.toString().equals("ac2d97712095a558e31573f62f466a3f9d93990898b0ec79d7c974c1780d524a")) throw new IOException("Checksum do modelo invalido"); if (!temporario.renameTo(modelo)) throw new IOException("Falha ao guardar modelo"); } if (fechado) return; // Um executor já ativo (teste ADB) pode ser reutilizado durante a transição. try { HttpURLConnection health = (HttpURLConnection)new URL("http://127.0.0.1:8101/health").openConnection(); health.setConnectTimeout(500); health.setReadTimeout(500); try { if (health.getResponseCode() == 200) { Log.i("CarinhaIA", "Executor existente pronto"); return; } } finally { health.disconnect(); } } catch (IOException ignored) { } ProcessBuilder builder = new ProcessBuilder(executavel.getAbsolutePath(), "-m", modelo.getAbsolutePath(), "--host", "0.0.0.0", "--port", "8101", "-c", "1024", "-t", "2", "-np", "1", "--jinja"); builder.environment().put("LD_LIBRARY_PATH", libs.getAbsolutePath()); builder.environment().put("GGML_BACKEND_DL_PATH", libs.getAbsolutePath()); builder.redirectErrorStream(true); builder.redirectOutput(new File(contexto.getFilesDir(), "ia-runtime.log")); synchronized (this) { if (!fechado) processo = builder.start(); } Log.i("CarinhaIA", "Executor nativo iniciado"); } catch (Exception e) { Log.e("CarinhaIA", "IA local: " + e); } }, "CarinhaIA").start(); } public synchronized void fechar() { fechado = true; if (processo != null) processo.destroy(); } }