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();