|
1 | 1 | package com.termux.app;
|
2 | 2 |
|
| 3 | +import android.app.Activity; |
| 4 | +import android.app.IntentService; |
3 | 5 | import android.app.Notification;
|
4 | 6 | import android.app.NotificationManager;
|
5 | 7 | import android.app.Service;
|
|
104 | 106 | * the command. This can add details about the command. 3rd party apps can provide more info
|
105 | 107 | * to users for setting up commands. Ideally a url link should be provided that goes into full
|
106 | 108 | * details.
|
| 109 | + * 9. The {@code Parcelable} {@link RUN_COMMAND_SERVICE#EXTRA_PENDING_INTENT} extra containing the |
| 110 | + * pending intent with which result of commands should be returned to the caller. The results |
| 111 | + * will be sent in the {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE} bundle. This is optional |
| 112 | + * and only needed if caller wants the results back. |
107 | 113 | *
|
108 | 114 | *
|
109 | 115 | * The {@link RUN_COMMAND_SERVICE#EXTRA_COMMAND_PATH} and {@link RUN_COMMAND_SERVICE#EXTRA_WORKDIR}
|
|
132 | 138 | * https://developer.android.com/training/basics/intents/package-visibility#package-name
|
133 | 139 | *
|
134 | 140 | *
|
| 141 | + * Its probably wiser for apps to import the {@link TermuxConstants} class and use the variables |
| 142 | + * provided for actions and extras instead of using hardcoded string values. |
135 | 143 | *
|
136 | 144 | * Sample code to run command "top" with java:
|
137 |
| - * Intent intent = new Intent(); |
138 |
| - * intent.setClassName("com.termux", "com.termux.app.RunCommandService"); |
139 |
| - * intent.setAction("com.termux.RUN_COMMAND"); |
140 |
| - * intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/top"); |
141 |
| - * intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{"-n", "5"}); |
142 |
| - * intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home"); |
143 |
| - * intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false); |
144 |
| - * intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0"); |
145 |
| - * startService(intent); |
| 145 | + * ``` |
| 146 | + * intent.setClassName("com.termux", "com.termux.app.RunCommandService"); |
| 147 | + * intent.setAction("com.termux.RUN_COMMAND"); |
| 148 | + * intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/top"); |
| 149 | + * intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{"-n", "5"}); |
| 150 | + * intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home"); |
| 151 | + * intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false); |
| 152 | + * intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0"); |
| 153 | + * startService(intent); |
| 154 | + * ``` |
146 | 155 | *
|
147 | 156 | * Sample code to run command "top" with "am startservice" command:
|
| 157 | + * ``` |
148 | 158 | * am startservice --user 0 -n com.termux/com.termux.app.RunCommandService \
|
149 | 159 | * -a com.termux.RUN_COMMAND \
|
150 | 160 | * --es com.termux.RUN_COMMAND_PATH '/data/data/com.termux/files/usr/bin/top' \
|
151 | 161 | * --esa com.termux.RUN_COMMAND_ARGUMENTS '-n,5' \
|
152 | 162 | * --es com.termux.RUN_COMMAND_WORKDIR '/data/data/com.termux/files/home' \
|
153 | 163 | * --ez com.termux.RUN_COMMAND_BACKGROUND 'false' \
|
154 | 164 | * --es com.termux.RUN_COMMAND_SESSION_ACTION '0'
|
| 165 | + * |
| 166 | + * |
| 167 | + * |
| 168 | + * |
| 169 | + * The {@link RUN_COMMAND_SERVICE#ACTION_RUN_COMMAND} intent returns the following extras |
| 170 | + * in the {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE} bundle if a pending intent is sent by the |
| 171 | + * called in {@code Parcelable} {@link RUN_COMMAND_SERVICE#EXTRA_PENDING_INTENT} extra: |
| 172 | + * |
| 173 | + * For foreground commands ({@link RUN_COMMAND_SERVICE#EXTRA_BACKGROUND} is `false`): |
| 174 | + * - {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT} will contain session transcript. |
| 175 | + * - {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_STDERR} will be null since its not used. |
| 176 | + * - {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_EXIT_CODE} will contain exit code of session. |
| 177 | +
|
| 178 | + * For background commands ({@link RUN_COMMAND_SERVICE#EXTRA_BACKGROUND} is `true`): |
| 179 | + * - {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT} will contain stdout of commands. |
| 180 | + * - {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_STDERR} will contain stderr of commands. |
| 181 | + * - {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_EXIT_CODE} will contain exit code of command. |
| 182 | + * |
| 183 | + * The {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT_ORIGINAL_LENGTH} and |
| 184 | + * {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_STDERR_ORIGINAL_LENGTH} will contain |
| 185 | + * the original length of stdout and stderr respectively. This is useful to detect cases where |
| 186 | + * stdout and stderr was too large to be sent back via an intent, otherwise |
| 187 | + * |
| 188 | + * The internal errors raised by termux outside the shell will be sent in the the |
| 189 | + * {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_ERR} and {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_ERRMSG} |
| 190 | + * extras. These will contain errors like if starting a termux command failed or if the user manually |
| 191 | + * exited the termux sessions or android killed the termux service before the commands had finished executing. |
| 192 | + * The err value will be {@link Activity#RESULT_OK}(-1) if no internal errors are raised. |
| 193 | + * |
| 194 | + * Note that if stdout or stderr are too large in length, then a {@link android.os.TransactionTooLargeException} |
| 195 | + * exception will be raised when the pending intent is sent back containing the results, But it cannot |
| 196 | + * be caught by the intent sender and intent will silently fail with logcat entries for the exception |
| 197 | + * raised internally by android os components. To prevent this, the stdout and stderr sent |
| 198 | + * back will be truncated from the start to max 100KB combined. The original length of stdout and |
| 199 | + * stderr will be provided in |
| 200 | + * {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT_ORIGINAL_LENGTH} and |
| 201 | + * {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE_STDERR_ORIGINAL_LENGTH} extras respectively, so |
| 202 | + * that the caller can check if either of them were truncated. The errmsg will also be truncated |
| 203 | + * from end to max 25KB to preserve start of stacktraces. |
| 204 | + * |
| 205 | + * |
| 206 | + * |
| 207 | + * If your app (not shell) wants to receive termux session command results, then put the |
| 208 | + * pending intent for your app like for an {@link IntentService} in the "com.termux.RUN_COMMAND_PENDING_INTENT" |
| 209 | + * extra. |
| 210 | + * ``` |
| 211 | + * // Create intent for your IntentService class |
| 212 | + * Intent pluginResultsServiceIntent = new Intent(MainActivity.this, PluginResultsService.class); |
| 213 | + * // Create PendingIntent that will be used by termux service to send result of commands back to PluginResultsService |
| 214 | + * PendingIntent pendingIntent = PendingIntent.getService(context, 1, pluginResultsServiceIntent, PendingIntent.FLAG_ONE_SHOT); |
| 215 | + * intent.putExtra("com.termux.RUN_COMMAND_PENDING_INTENT", pendingIntent); |
| 216 | + * ``` |
| 217 | + * |
| 218 | + * |
| 219 | + * Declare `PluginResultsService` entry in AndroidManifest.xml |
| 220 | + * ``` |
| 221 | + * <service android:name=".PluginResultsService" /> |
| 222 | + * ``` |
| 223 | + * |
| 224 | + * |
| 225 | + * Define the `PluginResultsService` class |
| 226 | + * ``` |
| 227 | + * public class PluginResultsService extends IntentService { |
| 228 | + * |
| 229 | + * public static final String PLUGIN_SERVICE_LABEL = "PluginResultsService"; |
| 230 | + * |
| 231 | + * private static final String LOG_TAG = "PluginResultsService"; |
| 232 | + * |
| 233 | + * public PluginResultsService(){ |
| 234 | + * super(PLUGIN_SERVICE_LABEL); |
| 235 | + * } |
| 236 | + * |
| 237 | + * @Override |
| 238 | + * protected void onHandleIntent(@Nullable Intent intent) { |
| 239 | + * if (intent == null) return; |
| 240 | + * |
| 241 | + * if(intent.getComponent() != null) |
| 242 | + * Log.d(LOG_TAG, PLUGIN_SERVICE_LABEL + " received execution result from " + intent.getComponent().toString()); |
| 243 | + * |
| 244 | + * |
| 245 | + * final Bundle resultBundle = intent.getBundleExtra("result"); |
| 246 | + * if (resultBundle == null) { |
| 247 | + * Log.e(LOG_TAG, "The intent does not contain the result bundle at the \"result\" key."); |
| 248 | + * return; |
| 249 | + * } |
| 250 | + * |
| 251 | + * Log.d(LOG_TAG, "stdout:\n```\n" + resultBundle.getString("stdout", "") + "\n```\n" + |
| 252 | + * "stdout_original_length: `" + resultBundle.getString("stdout_original_length") + "`\n" + |
| 253 | + * "stderr:\n```\n" + resultBundle.getString("stderr", "") + "\n```\n" + |
| 254 | + * "stderr_original_length: `" + resultBundle.getString("stderr_original_length") + "`\n" + |
| 255 | + * "exitCode: `" + resultBundle.getInt("exitCode") + "`\n" + |
| 256 | + * "errCode: `" + resultBundle.getInt("err") + "`\n" + |
| 257 | + * "errmsg: `" + resultBundle.getString("errmsg", "") + "`"); |
| 258 | + * } |
| 259 | + * |
| 260 | + * } |
| 261 | + *``` |
| 262 | + * |
| 263 | + * |
| 264 | + * |
| 265 | + * |
| 266 | + * |
| 267 | + * A service that receives {@link RUN_COMMAND_SERVICE#ACTION_RUN_COMMAND} intent from third party apps and |
| 268 | + * plugins that contains info on command execution and forwards the extras to {@link TermuxService} |
| 269 | + * for the actual execution. |
155 | 270 | */
|
156 | 271 | public class RunCommandService extends Service {
|
157 | 272 |
|
@@ -206,6 +321,9 @@ public int onStartCommand(Intent intent, int flags, int startId) {
|
206 | 321 | executionCommand.commandLabel = TextDataUtils.getDefaultIfNull(intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_LABEL), "RUN_COMMAND Execution Intent Command");
|
207 | 322 | executionCommand.commandDescription = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_DESCRIPTION);
|
208 | 323 | executionCommand.commandHelp = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_HELP);
|
| 324 | + executionCommand.pluginPendingIntent = intent.getParcelableExtra(RUN_COMMAND_SERVICE.EXTRA_PENDING_INTENT); |
| 325 | + |
| 326 | + |
209 | 327 |
|
210 | 328 | if(!executionCommand.setState(ExecutionState.PRE_EXECUTION))
|
211 | 329 | return Service.START_NOT_STICKY;
|
@@ -286,6 +404,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
|
286 | 404 | execIntent.putExtra(TERMUX_SERVICE.EXTRA_COMMAND_DESCRIPTION, executionCommand.commandDescription);
|
287 | 405 | execIntent.putExtra(TERMUX_SERVICE.EXTRA_COMMAND_HELP, executionCommand.commandHelp);
|
288 | 406 | execIntent.putExtra(TERMUX_SERVICE.EXTRA_PLUGIN_API_HELP, executionCommand.pluginAPIHelp);
|
| 407 | + execIntent.putExtra(TERMUX_SERVICE.EXTRA_PENDING_INTENT, executionCommand.pluginPendingIntent); |
289 | 408 |
|
290 | 409 | // Start TERMUX_SERVICE and pass it execution intent
|
291 | 410 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
0 commit comments