From 4c8296e93881a30871155aa25af03d46f429085d Mon Sep 17 00:00:00 2001
From: tareksander <57038324+tareksander@users.noreply.github.com>
Date: Wed, 1 Dec 2021 19:03:58 +0100
Subject: [PATCH 1/2] Changed: Expose a unix server socket
So clients can connect without having to call am if the plugin is
already running.
---
app/src/main/AndroidManifest.xml | 5 +
app/src/main/java/com/termux/api/App.java | 217 ++++++++++++++++++
.../java/com/termux/api/KeepAliveService.java | 21 ++
app/src/main/res/values/strings.xml | 2 +-
4 files changed, 244 insertions(+), 1 deletion(-)
create mode 100644 app/src/main/java/com/termux/api/App.java
create mode 100644 app/src/main/java/com/termux/api/KeepAliveService.java
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ff328bb67..91cce47c3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -106,6 +106,11 @@
+
diff --git a/app/src/main/java/com/termux/api/App.java b/app/src/main/java/com/termux/api/App.java
new file mode 100644
index 000000000..d51b7e855
--- /dev/null
+++ b/app/src/main/java/com/termux/api/App.java
@@ -0,0 +1,217 @@
+package com.termux.api;
+
+import android.app.Application;
+import android.content.Intent;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+
+import com.termux.api.util.TermuxApiLogger;
+
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class App extends Application
+{
+ public static final String LISTEN_ADDRESS = "com.termux.api://listen";
+ private static final Pattern EXTRA_STRING = Pattern.compile("(-e|--es|--esa) +([^ ]+) +\"(.*?)(? {
+ try (LocalServerSocket listen = new LocalServerSocket(LISTEN_ADDRESS)) {
+ while (true) {
+ try (LocalSocket con = listen.accept();
+ DataInputStream in = new DataInputStream(con.getInputStream());
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(con.getOutputStream()))) {
+ // only accept connections from Termux programs
+ if (con.getPeerCredentials().getUid() != getApplicationInfo().uid) {
+ continue;
+ }
+ try {
+ //System.out.println("connection");
+ int length = in.readUnsignedShort();
+ byte[] b = new byte[length];
+ in.readFully(b);
+ String cmdline = new String(b, StandardCharsets.UTF_8);
+
+ //System.out.println(cmdline.replaceAll("--es socket_input \".*?\"","").replaceAll("--es socket_output \".*?\"",""));
+ HashMap stringExtras = new HashMap<>();
+ HashMap stringArrayExtras = new HashMap<>();
+ HashMap booleanExtras = new HashMap<>();
+ HashMap intExtras = new HashMap<>();
+ HashMap floatExtras = new HashMap<>();
+ HashMap intArrayExtras = new HashMap<>();
+ HashMap longArrayExtras = new HashMap<>();
+ boolean err = false;
+
+ // extract and remove the string extras first, so another argument embedded in a string isn't counted as an argument
+ Matcher m = EXTRA_STRING.matcher(cmdline);
+ while (m.find()) {
+ String option = m.group(1);
+ if ("-e".equals(option) || "--es".equals(option)) {
+ // unescape "
+ stringExtras.put(m.group(2), Objects.requireNonNull(m.group(3)).replaceAll("\\\\\"","\""));
+ } else {
+ // split the list
+ String[] list = Objects.requireNonNull(m.group(3)).split("(? e : stringExtras.entrySet()) {
+ i.putExtra(e.getKey(), e.getValue());
+ }
+ for (Map.Entry e : stringArrayExtras.entrySet()) {
+ i.putExtra(e.getKey(), e.getValue());
+ }
+ for (Map.Entry e : intExtras.entrySet()) {
+ i.putExtra(e.getKey(), e.getValue());
+ }
+ for (Map.Entry e : booleanExtras.entrySet()) {
+ i.putExtra(e.getKey(), e.getValue());
+ }
+ for (Map.Entry e : floatExtras.entrySet()) {
+ i.putExtra(e.getKey(), e.getValue());
+ }
+ for (Map.Entry e : intArrayExtras.entrySet()) {
+ i.putExtra(e.getKey(), e.getValue());
+ }
+ for (Map.Entry e : longArrayExtras.entrySet()) {
+ i.putExtra(e.getKey(), e.getValue());
+ }
+ getApplicationContext().sendOrderedBroadcast(i, null);
+ // send a null byte as a sign that the arguments have been successfully received, parsed and the broadcast receiver is called
+ con.getOutputStream().write(0);
+ con.getOutputStream().flush();
+ } catch (Exception e) {
+ TermuxApiLogger.error("Error parsing arguments", e);
+ out.write("Exception in the plugin\n");
+ out.flush();
+ }
+ }
+ }
+ } catch (Exception e) {
+ TermuxApiLogger.error("Error listening for connections", e);
+ }
+ }).start();
+ }
+
+}
diff --git a/app/src/main/java/com/termux/api/KeepAliveService.java b/app/src/main/java/com/termux/api/KeepAliveService.java
new file mode 100644
index 000000000..666ca27aa
--- /dev/null
+++ b/app/src/main/java/com/termux/api/KeepAliveService.java
@@ -0,0 +1,21 @@
+package com.termux.api;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+public class KeepAliveService extends Service
+{
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return Service.START_STICKY;
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3c789642a..682868c7e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -11,6 +11,6 @@
&TERMUX_API_APP_NAME;
Share with
Grant permission
-
+ This service keeps Termux:API running in the background for faster startup of termux-* commands.
This app needs the following permission(s):\n
From 9e496d69334eab7ac0f5061c93367f66e663f8ff Mon Sep 17 00:00:00 2001
From: tareksander <57038324+tareksander@users.noreply.github.com>
Date: Thu, 2 Dec 2021 19:23:47 +0100
Subject: [PATCH 2/2] Added: Support for Intent actions over the socket
connection.
---
app/src/main/java/com/termux/api/App.java | 28 ++++++++++++++---------
1 file changed, 17 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/com/termux/api/App.java b/app/src/main/java/com/termux/api/App.java
index d51b7e855..82b85b0be 100644
--- a/app/src/main/java/com/termux/api/App.java
+++ b/app/src/main/java/com/termux/api/App.java
@@ -27,7 +27,7 @@ public class App extends Application
private static final Pattern EXTRA_INT_LIST = Pattern.compile("--eia +([^ ]+) +(-?[0-9]+(?:,-?[0-9]+)*)");
private static final Pattern EXTRA_LONG_LIST = Pattern.compile("--ela +([^ ]+) +(-?[0-9]+(?:,-?[0-9]+)*)");
private static final Pattern EXTRA_UNSUPPORTED = Pattern.compile("--e[^izs ] +[^ ]+ +[^ ]+");
-
+ private static final Pattern ACTION = Pattern.compile("-a *([^ ]+)");
@Override
public void onCreate() {
@@ -49,6 +49,7 @@ public void onCreate() {
in.readFully(b);
String cmdline = new String(b, StandardCharsets.UTF_8);
+ Intent intent = new Intent(getApplicationContext(), TermuxApiReceiver.class);
//System.out.println(cmdline.replaceAll("--es socket_input \".*?\"","").replaceAll("--es socket_output \".*?\"",""));
HashMap stringExtras = new HashMap<>();
HashMap stringArrayExtras = new HashMap<>();
@@ -151,6 +152,12 @@ public void onCreate() {
}
cmdline = m.replaceAll("");
+ m = ACTION.matcher(cmdline);
+ while (m.find()) {
+ intent.setAction(m.group(1));
+ }
+ cmdline = m.replaceAll("");
+
m = EXTRA_UNSUPPORTED.matcher(cmdline);
if (m.find()) {
String msg = "Unsupported argument type: " + m.group(0) + "\n";
@@ -174,30 +181,29 @@ public void onCreate() {
continue;
}
- // construct the intent with the extras
- Intent i = new Intent(getApplicationContext(), TermuxApiReceiver.class);
+ // set the intent extras
for (Map.Entry e : stringExtras.entrySet()) {
- i.putExtra(e.getKey(), e.getValue());
+ intent.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry e : stringArrayExtras.entrySet()) {
- i.putExtra(e.getKey(), e.getValue());
+ intent.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry e : intExtras.entrySet()) {
- i.putExtra(e.getKey(), e.getValue());
+ intent.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry e : booleanExtras.entrySet()) {
- i.putExtra(e.getKey(), e.getValue());
+ intent.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry e : floatExtras.entrySet()) {
- i.putExtra(e.getKey(), e.getValue());
+ intent.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry e : intArrayExtras.entrySet()) {
- i.putExtra(e.getKey(), e.getValue());
+ intent.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry e : longArrayExtras.entrySet()) {
- i.putExtra(e.getKey(), e.getValue());
+ intent.putExtra(e.getKey(), e.getValue());
}
- getApplicationContext().sendOrderedBroadcast(i, null);
+ getApplicationContext().sendOrderedBroadcast(intent, null);
// send a null byte as a sign that the arguments have been successfully received, parsed and the broadcast receiver is called
con.getOutputStream().write(0);
con.getOutputStream().flush();