Skip to content

Commit 31371b5

Browse files
Fully integrate ExectionCommand into RunCommandService
Users will now also be shown flashes and notifications in addition to log entries for missing allow-external-apps permission or for invalid extras passed like the executable. The flashes and notifications can be controlled with the Termux Settings -> Debugging -> Plugin Error Notifications toggle
1 parent ef1ab19 commit 31371b5

File tree

5 files changed

+250
-136
lines changed

5 files changed

+250
-136
lines changed

app/src/main/java/com/termux/app/RunCommandService.java

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@
1010
import android.os.Binder;
1111
import android.os.Build;
1212
import android.os.IBinder;
13-
import android.util.Log;
1413

1514
import com.termux.R;
1615
import com.termux.app.TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE;
1716
import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
1817
import com.termux.app.utils.FileUtils;
1918
import com.termux.app.utils.Logger;
2019
import com.termux.app.utils.PluginUtils;
21-
22-
import java.util.Arrays;
23-
import java.util.HashMap;
20+
import com.termux.app.utils.TextDataUtils;
21+
import com.termux.models.ExecutionCommand;
22+
import com.termux.models.ExecutionCommand.ExecutionState;
2423

2524
/**
2625
* Third-party apps that are not part of termux world can run commands in termux context by either
@@ -88,25 +87,45 @@
8887
*
8988
* The {@link RUN_COMMAND_SERVICE#ACTION_RUN_COMMAND} intent expects the following extras:
9089
*
91-
* 1. The {@code String} {@link RUN_COMMAND_SERVICE#EXTRA_COMMAND_PATH} extra for absolute path of
92-
* command. This is mandatory.
90+
* 1. The **mandatory** {@code String} {@link RUN_COMMAND_SERVICE#EXTRA_COMMAND_PATH} extra for
91+
* absolute path of command.
9392
* 2. The {@code String[]} {@link RUN_COMMAND_SERVICE#EXTRA_ARGUMENTS} extra for any arguments to
94-
* pass to command. This is optional.
93+
* pass to command.
9594
* 3. The {@code String} {@link RUN_COMMAND_SERVICE#EXTRA_WORKDIR} extra for current working directory
96-
* of command. This is optional and defaults to {@link TermuxConstants#TERMUX_HOME_DIR_PATH}.
95+
* of command. This defaults to {@link TermuxConstants#TERMUX_HOME_DIR_PATH}.
9796
* 4. The {@code boolean} {@link RUN_COMMAND_SERVICE#EXTRA_BACKGROUND} extra whether to run command
98-
* in background or foreground terminal session. This is optional and defaults to {@code false}.
97+
* in background or foreground terminal session. This defaults to {@code false}.
9998
* 5. The {@code String} {@link RUN_COMMAND_SERVICE#EXTRA_SESSION_ACTION} extra for for session action
100-
* of foreground commands. This is optional and defaults to
99+
* of foreground commands. This defaults to
101100
* {@link TERMUX_SERVICE#VALUE_EXTRA_SESSION_ACTION_SWITCH_TO_NEW_SESSION_AND_OPEN_ACTIVITY}.
102-
*
101+
* 6. The {@code String} {@link RUN_COMMAND_SERVICE#EXTRA_COMMAND_LABEL} extra for label of the command.
102+
* 7. The markdown {@code String} {@link RUN_COMMAND_SERVICE#EXTRA_COMMAND_DESCRIPTION} extra for
103+
* description of the command. This should ideally be get short.
104+
* 8. The markdown {@code String} {@link RUN_COMMAND_SERVICE#EXTRA_COMMAND_HELP} extra for help of
105+
* the command. This can add details about the command. 3rd party apps can provide more info
106+
* to users for setting up commands. Ideally a url link should be provided that goes into full
107+
* details.
103108
*
104109
*
105110
* The {@link RUN_COMMAND_SERVICE#EXTRA_COMMAND_PATH} and {@link RUN_COMMAND_SERVICE#EXTRA_WORKDIR}
106111
* can optionally be prefixed with "$PREFIX/" or "~/" if an absolute path is not to be given.
107112
* The "$PREFIX/" will expand to {@link TermuxConstants#TERMUX_PREFIX_DIR_PATH} and
108113
* "~/" will expand to {@link TermuxConstants#TERMUX_HOME_DIR_PATH}, followed by a forward slash "/".
109114
*
115+
*
116+
* The `EXTRA_COMMAND_*` extras are used for logging and are their values are provided to users in case
117+
* of failure in a popup. The popup shown is in commonmark-spec markdown using markwon library so
118+
* make sure to follow its formatting rules. Also make sure to end lines with 2 blank spaces to prevent
119+
* word-wrap wherever needed.
120+
* It's the users and 3rd party apps responsibility to use them wisely. There are also android
121+
* internal intent size limits (roughly 500KB) that must not exceed when sending intents so make sure
122+
* the combined size of ALL extras is less than that.
123+
* There are also limits on the arguments size you can pass to commands or the full command string
124+
* length that can be run, which is likely equal to 131072 bytes or 128KB on an android device.
125+
* Check https://github.com/termux/termux-tasker#arguments-and-result-data-limits for more details.
126+
*
127+
*
128+
*
110129
* If your third-party app is targeting sdk 30 (android 11), then it needs to add `com.termux`
111130
* package to the `queries` element or request `QUERY_ALL_PACKAGES` permission in its
112131
* `AndroidManifest.xml`. Otherwise it will get `PackageSetting{...... com.termux/......} BLOCKED`
@@ -138,7 +157,7 @@
138157
public class RunCommandService extends Service {
139158

140159
private static final String NOTIFICATION_CHANNEL_ID = "termux_run_command_notification_channel";
141-
private static final int NOTIFICATION_ID = 1338;
160+
public static final int NOTIFICATION_ID = 1338;
142161

143162
private static final String LOG_TAG = "RunCommandService";
144163

@@ -166,78 +185,91 @@ public int onStartCommand(Intent intent, int flags, int startId) {
166185
// Run again in case service is already started and onCreate() is not called
167186
runStartForeground();
168187

188+
ExecutionCommand executionCommand = new ExecutionCommand();
189+
executionCommand.pluginAPIHelp = this.getString(R.string.run_command_service_api_help);
190+
169191
String errmsg;
170192

171193
// If invalid action passed, then just return
172194
if (!RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND.equals(intent.getAction())) {
173195
errmsg = this.getString(R.string.run_command_service_invalid_action, intent.getAction());
174-
Logger.logError(LOG_TAG, errmsg);
196+
executionCommand.setStateFailed(1, errmsg, null);
197+
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand);
175198
return Service.START_NOT_STICKY;
176199
}
177200

201+
executionCommand.executable = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH);
202+
executionCommand.arguments = intent.getStringArrayExtra(RUN_COMMAND_SERVICE.EXTRA_ARGUMENTS);
203+
executionCommand.workingDirectory = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_WORKDIR);
204+
executionCommand.inBackground = intent.getBooleanExtra(RUN_COMMAND_SERVICE.EXTRA_BACKGROUND, false);
205+
executionCommand.sessionAction = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_SESSION_ACTION);
206+
executionCommand.commandLabel = TextDataUtils.getDefaultIfNull(intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_LABEL), "RUN_COMMAND Execution Intent Command");
207+
executionCommand.commandDescription = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_DESCRIPTION);
208+
executionCommand.commandHelp = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_HELP);
209+
210+
if(!executionCommand.setState(ExecutionState.PRE_EXECUTION))
211+
return Service.START_NOT_STICKY;
212+
178213
// If "allow-external-apps" property to not set to "true", then just return
179214
errmsg = PluginUtils.checkIfRunCommandServiceAllowExternalAppsPolicyIsViolated(this);
180215
if (errmsg != null) {
181-
Logger.logError(LOG_TAG, errmsg);
216+
executionCommand.setStateFailed(1, errmsg, null);
217+
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand);
182218
return Service.START_NOT_STICKY;
183219
}
184220

185-
186-
String executable = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH);
187-
String[] arguments = intent.getStringArrayExtra(RUN_COMMAND_SERVICE.EXTRA_ARGUMENTS);
188-
boolean inBackground = intent.getBooleanExtra(RUN_COMMAND_SERVICE.EXTRA_BACKGROUND, false);
189-
String workingDirectory = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_WORKDIR);
190-
String sessionAction = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_SESSION_ACTION);
191-
192221
// Get canonical path of executable
193-
executable = FileUtils.getCanonicalPath(executable, null, true);
222+
executionCommand.executable = FileUtils.getCanonicalPath(executionCommand.executable, null, true);
194223

195224
// If executable is not a regular file, or is not readable or executable, then just return
196225
// Setting of missing read and execute permissions is not done
197-
errmsg = FileUtils.validateRegularFileExistenceAndPermissions(this, executable,
226+
errmsg = FileUtils.validateRegularFileExistenceAndPermissions(this, executionCommand.executable,
198227
null, PluginUtils.PLUGIN_EXECUTABLE_FILE_PERMISSIONS,
199228
false, false);
200229
if (errmsg != null) {
201-
errmsg += "\n" + this.getString(R.string.executable_absolute_path, executable);
202-
Logger.logError(LOG_TAG, errmsg);
230+
errmsg += "\n" + this.getString(R.string.executable_absolute_path, executionCommand.executable);
231+
executionCommand.setStateFailed(1, errmsg, null);
232+
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand);
203233
return Service.START_NOT_STICKY;
204234
}
205235

206236
// If workingDirectory is not null or empty
207-
if (workingDirectory != null && !workingDirectory.isEmpty()) {
237+
if (executionCommand.workingDirectory != null && !executionCommand.workingDirectory.isEmpty()) {
208238
// Get canonical path of workingDirectory
209-
workingDirectory = FileUtils.getCanonicalPath(workingDirectory, null, true);
239+
executionCommand.workingDirectory = FileUtils.getCanonicalPath(executionCommand.workingDirectory, null, true);
210240

211241
// If workingDirectory is not a directory, or is not readable or writable, then just return
212242
// Creation of missing directory and setting of read, write and execute permissions are only done if workingDirectory is
213243
// under {@link TermuxConstants#TERMUX_FILES_DIR_PATH}
214244
// We try to set execute permissions, but ignore if they are missing, since only read and write permissions are required
215245
// for working directories.
216-
errmsg = FileUtils.validateDirectoryExistenceAndPermissions(this, workingDirectory,
246+
errmsg = FileUtils.validateDirectoryExistenceAndPermissions(this, executionCommand.workingDirectory,
217247
TermuxConstants.TERMUX_FILES_DIR_PATH, PluginUtils.PLUGIN_WORKING_DIRECTORY_PERMISSIONS,
218248
true, true, false,
219249
true);
220250
if (errmsg != null) {
221-
errmsg += "\n" + this.getString(R.string.working_directory_absolute_path, workingDirectory);
222-
Logger.logError(LOG_TAG, errmsg);
251+
errmsg += "\n" + this.getString(R.string.working_directory_absolute_path, executionCommand.workingDirectory);
252+
executionCommand.setStateFailed(1, errmsg, null);
253+
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand);
223254
return Service.START_NOT_STICKY;
224255
}
225256
}
226257

227-
PluginUtils.dumpExecutionIntentToLog(Log.VERBOSE, LOG_TAG, "RUN_COMMAND Intent", executable, Arrays.asList(arguments), workingDirectory, inBackground, new HashMap<String, Object>() {{
228-
put("sessionAction", sessionAction);
229-
}});
258+
executionCommand.executableUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(FileUtils.getExpandedTermuxPath(executionCommand.executable)).build();
230259

231-
Uri executableUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(FileUtils.getExpandedTermuxPath(executable)).build();
260+
Logger.logVerbose(LOG_TAG, executionCommand.toString());
232261

233262
// Create execution intent with the action TERMUX_SERVICE#ACTION_SERVICE_EXECUTE to be sent to the TERMUX_SERVICE
234-
Intent execIntent = new Intent(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE, executableUri);
263+
Intent execIntent = new Intent(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE, executionCommand.executableUri);
235264
execIntent.setClass(this, TermuxService.class);
236-
execIntent.putExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS, arguments);
237-
execIntent.putExtra(TERMUX_SERVICE.EXTRA_BACKGROUND, inBackground);
238-
if (workingDirectory != null && !workingDirectory.isEmpty()) execIntent.putExtra(TERMUX_SERVICE.EXTRA_WORKDIR, workingDirectory);
239-
execIntent.putExtra(TERMUX_SERVICE.EXTRA_SESSION_ACTION, sessionAction);
240-
265+
execIntent.putExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS, executionCommand.arguments);
266+
if (executionCommand.workingDirectory != null && !executionCommand.workingDirectory.isEmpty()) execIntent.putExtra(TERMUX_SERVICE.EXTRA_WORKDIR, executionCommand.workingDirectory);
267+
execIntent.putExtra(TERMUX_SERVICE.EXTRA_BACKGROUND, executionCommand.inBackground);
268+
execIntent.putExtra(TERMUX_SERVICE.EXTRA_SESSION_ACTION, executionCommand.sessionAction);
269+
execIntent.putExtra(TERMUX_SERVICE.EXTRA_COMMAND_LABEL, executionCommand.commandLabel);
270+
execIntent.putExtra(TERMUX_SERVICE.EXTRA_COMMAND_DESCRIPTION, executionCommand.commandDescription);
271+
execIntent.putExtra(TERMUX_SERVICE.EXTRA_COMMAND_HELP, executionCommand.commandHelp);
272+
execIntent.putExtra(TERMUX_SERVICE.EXTRA_PLUGIN_API_HELP, executionCommand.pluginAPIHelp);
241273

242274
// Start TERMUX_SERVICE and pass it execution intent
243275
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -292,8 +324,8 @@ private void setupNotificationChannel() {
292324
int importance = NotificationManager.IMPORTANCE_LOW;
293325

294326
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, importance);
295-
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
296-
manager.createNotificationChannel(channel);
327+
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
328+
notificationManager.createNotificationChannel(channel);
297329
}
298330

299331
}

0 commit comments

Comments
 (0)