I'm developing a Unity app which will be able to interface with the Android app Termux via the Intent described here:
https://github.com/termux/termux-app/wiki/RUN_COMMAND-Intent#advance-examples
In my Unity-Android plugin I have two classes:
PluginInstance.java:
package com.saricden.exd2termux;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE;
import com.unity3d.player.UnityPlayer;
import java.util.Arrays;
public class PluginInstance {
private static Activity unityActivity;
public static void receiveUnityActivity(Activity tActivity) {
unityActivity = tActivity;
}
private String unityGameObjectName = null;
public void debugLog(String msg) {
UnityPlayer.UnitySendMessage("DebugParent/DebugListener", "LogMessage", "BIGTEST (" + msg + ")");
}
public void loginToTermux() {
Intent intent = new Intent();
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/login");
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", true);
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
unityActivity.startForegroundService(intent);
}
public void sendToUnity(String msg) {
UnityPlayer.UnitySendMessage(unityGameObjectName, "ReceiveTermuxOutput", msg);
}
public void execTermuxCMD(String cmd) {
debugLog("Started command...");
String[] cmdParts = cmd.split(" ");
String cmdRoot = cmdParts[0];
String[] cmdArgs = Arrays.copyOfRange(cmdParts, 1, cmdParts.length);
Intent intent = new Intent();
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/" + cmdRoot);
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", cmdArgs);
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", true);
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
Intent pluginResultsServiceIntent = new Intent(unityActivity, PluginResultsService.class);
int executionId = PluginResultsService.getNextExecutionId();
pluginResultsServiceIntent.putExtra(PluginResultsService.EXTRA_EXECUTION_ID, executionId);
PendingIntent pendingIntent = PendingIntent.getService(unityActivity, executionId, pluginResultsServiceIntent, PendingIntent.FLAG_ONE_SHOT);
intent.putExtra(RUN_COMMAND_SERVICE.EXTRA_PENDING_INTENT, pendingIntent);
unityActivity.startForegroundService(intent);
debugLog("Started foreground service, with run_background 'true'");
}
public void setUnityGameObjectName(String name) {
unityGameObjectName = name;
}
}
PluginResultsService.java:
package com.saricden.exd2termux;
import android.app.IntentService;
import android.content.Intent;
import androidx.annotation.Nullable;
import com.unity3d.player.UnityPlayer;
public class PluginResultsService extends IntentService {
public static final String EXTRA_EXECUTION_ID = "execution_id";
private static int EXECUTION_ID = 1000;
public static final String PLUGIN_SERVICE_LABEL = "PluginResultsService";
private static final String LOG_TAG = "PluginResultsService";
public PluginResultsService(){
super(PLUGIN_SERVICE_LABEL);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
UnityPlayer.UnitySendMessage("DebugParent/DebugListener", "LogMessage", "onHandleIntent!!!");
}
public static synchronized int getNextExecutionId() {
EXECUTION_ID++;
UnityPlayer.UnitySendMessage("DebugParent/DebugListener", "LogMessage", "Get next execution_id (" + EXECUTION_ID + ")");
return EXECUTION_ID;
}
}
Then finally, my Unity MonoBehaviour class that kicks it all off:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Vuplex.WebView;
public class TerminalManager : MonoBehaviour
{
private AndroidJavaClass unityClass;
private AndroidJavaObject unityActivity;
private AndroidJavaObject _pluginInstance;
private WebViewPrefab webViewPrefab;
void InitializePlugin(string pluginName) {
unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
unityActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
_pluginInstance = new AndroidJavaObject(pluginName);
if (_pluginInstance == null) {
Debug.Log("!!!!! >> PLUGIN INSTANCE ERROR << !!!!!");
}
_pluginInstance.CallStatic("receiveUnityActivity", unityActivity);
_pluginInstance.Call("loginToTermux");
}
async void Start() {
webViewPrefab = GetComponent<WebViewPrefab>();
InitializePlugin("com.saricden.exd2termux.PluginInstance");
_pluginInstance.Call("setUnityGameObjectName", transform.root.name + "/" + transform.name);
await webViewPrefab.WaitUntilInitialized();
webViewPrefab.WebView.MessageEmitted += (sender, eArgs) => {
Debug.Log("Dispatching Termux command...");
Debug.Log(eArgs.Value);
_pluginInstance.Call("execTermuxCMD", eArgs.Value);
};
}
public void ReceiveTermuxOutput(string res) {
Debug.Log("Receiving Termux response...");
webViewPrefab.WebView.ExecuteJavaScript("appendResponse(`" + res + "`)");
}
}
I can confirm the Termux commands are running. I have tested by running mkdir testFolder, then opening Termux and checking the home directory. The "testFolder" was successfully created.
The problem: PluginResultsService -> onHandleIntent never gets called!
How can I go about further debugging this? And/or does anyone have any suggestions as to what the problem might be?
I figured out the solution.
I was defining:
Inside my Unity app's top-level AndroidManifest.xml, but really it needed to be defined in the plugin-level AndroidManifest.xml!
Let's goo!