Added: termux-am-library to integrate am with Termux.

This commit is contained in:
tareksander 2021-12-04 19:28:32 +01:00 committed by agnostic-apollo
parent e597ece75f
commit 4aca16326c
3 changed files with 182 additions and 0 deletions

View File

@ -25,6 +25,10 @@ android {
implementation project(":terminal-view")
implementation project(":termux-shared")
implementation 'com.github.tareksander:termux-am-library:main-SNAPSHOT'
}
defaultConfig {

View File

@ -3,13 +3,16 @@ package com.termux.app;
import android.app.Application;
import android.content.Context;
import com.termux.am.Am;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.crash.TermuxCrashUtils;
import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences;
import com.termux.shared.logger.Logger;
import com.termux.shared.termux.settings.properties.TermuxAppSharedProperties;
import com.termux.shared.termux.theme.TermuxThemeUtils;
import com.termux.shared.shell.LocalSocketListener;
import java.io.IOException;
public class TermuxApplication extends Application {
@ -31,6 +34,20 @@ public class TermuxApplication extends Application {
// Set NightMode.APP_NIGHT_MODE
TermuxThemeUtils.setAppNightMode(properties.getNightMode());
try {
new LocalSocketListener(this, (args, out, err) -> {
try {
new Am(out, err, this).run(args);
return 0;
} catch (Exception e) {
return 1;
}
}, TermuxConstants.TERMUX_PACKAGE_NAME+"://call-am", 1000);
}
catch (IOException e) {
Logger.logDebug("TermuxApplication", "am socket already in use");
}
}
public static void setLogConfig(Context context) {

View File

@ -0,0 +1,161 @@
package com.termux.shared.shell;
import android.app.Application;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import androidx.annotation.NonNull;
import com.termux.shared.logger.Logger;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
public class LocalSocketListener
{
public interface LocalSocketHandler {
int handle(String[] args, PrintStream out, PrintStream err);
}
private static final String LOG_TAG = "LocalSocketListener";
private final Thread thread;
private final LocalServerSocket server;
private final int timeoutMillis;
public LocalSocketListener(@NonNull Application a, @NonNull LocalSocketHandler h, String address, int timeoutMillis) throws IOException {
this.timeoutMillis = timeoutMillis;
server = new LocalServerSocket(address);
thread = new Thread(new LocalSocketListenerRunnable(a, h));
thread.setUncaughtExceptionHandler((t, e) -> Logger.logStackTraceWithMessage(LOG_TAG, "Uncaught exception in LocalSocketListenerRunnable", e));
thread.start();
}
@SuppressWarnings("unused")
public void stop() {
try {
thread.interrupt();
server.close();
} catch (Exception ignored) {}
}
private class LocalSocketListenerRunnable implements Runnable {
private final Application a;
private final TimeoutWatcher timeoutWatcher;
private final Thread timeoutWatcherThread;
private final LocalSocketHandler h;
public LocalSocketListenerRunnable(@NonNull Application a, @NonNull LocalSocketHandler h) {
this.a = a;
this.h = h;
timeoutWatcher = new TimeoutWatcher();
timeoutWatcherThread = new Thread(timeoutWatcher);
timeoutWatcherThread.start();
}
// the socket timeout for LocalSocket doesn't seem to work, so close the socket if the timeout is over, so the processing Thread doesn't get blocked.
private class TimeoutWatcher implements Runnable {
private final Object lock = new Object();
private LocalSocket current = null;
@Override
public void run() {
while (! Thread.currentThread().isInterrupted()) {
LocalSocket watch = current;
synchronized (lock) {
while (watch == null) {
try {
lock.wait();
}
catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
watch = current;
}
}
try {
//noinspection BusyWait
Thread.sleep(timeoutMillis);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
try {
watch.shutdownInput();
} catch (Exception ignored) {}
try {
watch.shutdownOutput();
} catch (Exception ignored) {}
try {
watch.close();
} catch (Exception ignored) {}
}
}
}
@Override
public void run() {
try {
while (! Thread.currentThread().isInterrupted()) {
try (LocalSocket s = server.accept();
OutputStream sockout = s.getOutputStream();
InputStreamReader r = new InputStreamReader(s.getInputStream())) {
timeoutWatcher.current = s;
synchronized (timeoutWatcher.lock) {
timeoutWatcher.lock.notifyAll();
}
// ensure only Termux programs can connect
if (s.getPeerCredentials().getUid() != a.getApplicationInfo().uid) {
Logger.logDebug(LOG_TAG, "A program with another UID tried to connect");
continue;
}
StringBuilder b = new StringBuilder();
int c;
while ((c = r.read()) > 0) {
b.append((char) c);
}
String outString;
String errString;
int ret;
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream outp = new PrintStream(out);
ByteArrayOutputStream err = new ByteArrayOutputStream();
PrintStream errp = new PrintStream(err)) {
ret = h.handle(ArgumentTokenizer.tokenize(b.toString()).toArray(new String[0]), outp, errp);
outp.flush();
outString = out.toString("UTF-8");
errp.flush();
errString = err.toString("UTF-8");
}
try (BufferedWriter w = new BufferedWriter(new OutputStreamWriter(sockout))) {
w.write(Integer.toString(ret));
w.write('\0');
w.write(outString);
w.write('\0');
w.write(errString);
w.flush();
}
} catch (Exception e) {
Logger.logStackTraceWithMessage(LOG_TAG, "Exception while handling connection", e);
}
}
}
finally {
try {
server.close();
} catch (Exception ignored) {}
if (timeoutWatcherThread.isAlive()) {
timeoutWatcherThread.interrupt();
}
}
Logger.logDebug(LOG_TAG, "LocalSocketListenerRunnable returned");
}
}
}