mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-07 03:05:18 +08:00
When `Logger.CURRENT_LOG_LEVEL` set by user is `Logger.LOG_VERBOSE`, then background (not foreground sessions) command output was being logged to logcat, however, if command outputted too much data to logcat, then logcat clients like in Android Studio would crash. Also if a logcat dump is being taken inside termux, then duplicate lines would occur, first one due to of original entry, and second one due to StreamGobbler logging output at verbose level for logcat command. This would be a concern for plugins as well like `RUN_COMMAND` intent or Termux:Tasker, etc if they ran commands with lot of data and user had set log level to verbose. For plugins, TermuxService now supports `com.termux.execute.background_custom_log_level` `String` extra for custom log level. Termux:Tasker, etc will have to be updated with support. For `RUN_COMMAND` intent, the `com.termux.RUN_COMMAND_BACKGROUND_CUSTOM_LOG_LEVEL` `String` extra is now provided to set custom log level for only the command output. Check `TermuxConstants`. So one can pass a custom log level that is `>=` to the log level set it termux settings where (OFF=0, NORMAL=1, DEBUG=2, VERBOSE=3). If you pass `0`, it will completely disable logging. If you pass `1`, logging will only be enabled if log level in termux settings is `NORMAL` or higher. If custom log level is not passed, then old behaviour will remain and log level in termux settings must be `VERBOSE` or higher for logging to be enabled. Note that the log entries will still be logged with priority `Log.VERBOSE` regardless of log level, i.e `logcat` will have `V/`. The entries logcat component has now changed from `StreamGobbler` to `TermuxCommand`. For output at `stdout`, the entry format is `[<pid>-stdout] ...` and for the output at `stderr`, the entry format is `[<pid>-stderr] ...`. The `<pid>` will be process id as an integer that was started by termux. For example: `V/TermuxCommand: [66666-stdout] ...`. While doing this I realize that instead of using `am` command to send messages back to tasker, you can use tasker `Logcat Entry` profile event to listen to messages from termux at both `stdout` and `stderr`. This might be faster than `am` command intent systems or at least possibly more convenient in some use cases. So setup a profile with the `Component` value set to `TermuxCommand` and `Filter` value set to `-E 'TermuxCommand: \[[0-9]+-((stdout)|(stderr))\] message_tag: .*'` and enable the `Grep Filter` toggle so that entry matching is done in native code. Check https://github.com/joaomgcd/TaskerDocumentation/blob/master/en/help/logcat%20info.md for details. Also enable `Enforce Task Order` in profile settings and set collision handling to `Run Both Together` so that if two or more entries are sent quickly, entry task is run for all. Tasker currently (v5.13.16) is not maintaining order of entry tasks despite the setting. Then you can send an intent from tasker via `Run Shell` action with `root` (since `am` command won't work without it on android >=8) or normally in termux from a script, you should be able to receive the entries as `@lc_text` in entry task of tasker `Logcat Entry` profile. The following just passes two `echo` commands to `bash` as a script via `stdin`. If you don't have root, then you can call a wrapper script with `TermuxCommand` function in `Tasker Function` action that sends another `RUN_COMMAND` intent with termux provide `am` command which will work without root. ``` am startservice --user 0 -n com.termux/com.termux.app.RunCommandService -a com.termux.RUN_COMMAND --es com.termux.RUN_COMMAND_PATH '/data/data/com.termux/files/usr/bin/bash' --es com.termux.RUN_COMMAND_STDIN 'echo "message_tag: Sending message from tasker to termux"' --ez com.termux.RUN_COMMAND_BACKGROUND true --es com.termux.RUN_COMMAND_BACKGROUND_CUSTOM_LOG_LEVEL '1' ```
167 lines
6.4 KiB
Java
167 lines
6.4 KiB
Java
package com.termux.shared.data;
|
|
|
|
import android.content.Intent;
|
|
import android.os.Bundle;
|
|
import android.os.Parcelable;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import java.util.Arrays;
|
|
|
|
public class IntentUtils {
|
|
|
|
private static final String LOG_TAG = "IntentUtils";
|
|
|
|
|
|
/**
|
|
* Get a {@link String} extra from an {@link Intent} if its not {@code null} or empty.
|
|
*
|
|
* @param intent The {@link Intent} to get the extra from.
|
|
* @param key The {@link String} key name.
|
|
* @param def The default value if extra is not set.
|
|
* @param throwExceptionIfNotSet If set to {@code true}, then an exception will be thrown if extra
|
|
* is not set.
|
|
* @return Returns the {@link String} extra if set, otherwise {@code null}.
|
|
*/
|
|
public static String getStringExtraIfSet(@NonNull Intent intent, String key, String def, boolean throwExceptionIfNotSet) throws Exception {
|
|
String value = getStringExtraIfSet(intent, key, def);
|
|
if (value == null && throwExceptionIfNotSet)
|
|
throw new Exception("The \"" + key + "\" key string value is null or empty");
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Get a {@link String} extra from an {@link Intent} if its not {@code null} or empty.
|
|
*
|
|
* @param intent The {@link Intent} to get the extra from.
|
|
* @param key The {@link String} key name.
|
|
* @param def The default value if extra is not set.
|
|
* @return Returns the {@link String} extra if set, otherwise {@code null}.
|
|
*/
|
|
public static String getStringExtraIfSet(@NonNull Intent intent, String key, String def) {
|
|
String value = intent.getStringExtra(key);
|
|
if (value == null || value.isEmpty()) {
|
|
if (def != null && !def.isEmpty())
|
|
return def;
|
|
else
|
|
return null;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Get an {@link Integer} from an {@link Intent} stored as a {@link String} extra if its not
|
|
* {@code null} or empty.
|
|
*
|
|
* @param intent The {@link Intent} to get the extra from.
|
|
* @param key The {@link String} key name.
|
|
* @param def The default value if extra is not set.
|
|
* @return Returns the {@link Integer} extra if set, otherwise {@code null}.
|
|
*/
|
|
public static Integer getIntegerExtraIfSet(@NonNull Intent intent, String key, Integer def) {
|
|
try {
|
|
String value = intent.getStringExtra(key);
|
|
if (value == null || value.isEmpty()) {
|
|
return def;
|
|
}
|
|
|
|
return Integer.parseInt(value);
|
|
}
|
|
catch (Exception e) {
|
|
return def;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Get a {@link String[]} extra from an {@link Intent} if its not {@code null} or empty.
|
|
*
|
|
* @param intent The {@link Intent} to get the extra from.
|
|
* @param key The {@link String} key name.
|
|
* @param def The default value if extra is not set.
|
|
* @param throwExceptionIfNotSet If set to {@code true}, then an exception will be thrown if extra
|
|
* is not set.
|
|
* @return Returns the {@link String[]} extra if set, otherwise {@code null}.
|
|
*/
|
|
public static String[] getStringArrayExtraIfSet(@NonNull Intent intent, String key, String[] def, boolean throwExceptionIfNotSet) throws Exception {
|
|
String[] value = getStringArrayExtraIfSet(intent, key, def);
|
|
if (value == null && throwExceptionIfNotSet)
|
|
throw new Exception("The \"" + key + "\" key string array is null or empty");
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Get a {@link String[]} extra from an {@link Intent} if its not {@code null} or empty.
|
|
*
|
|
* @param intent The {@link Intent} to get the extra from.
|
|
* @param key The {@link String} key name.
|
|
* @param def The default value if extra is not set.
|
|
* @return Returns the {@link String[]} extra if set, otherwise {@code null}.
|
|
*/
|
|
public static String[] getStringArrayExtraIfSet(Intent intent, String key, String[] def) {
|
|
String[] value = intent.getStringArrayExtra(key);
|
|
if (value == null || value.length == 0) {
|
|
if (def != null && def.length != 0)
|
|
return def;
|
|
else
|
|
return null;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public static String getIntentString(Intent intent) {
|
|
if (intent == null) return null;
|
|
|
|
return intent.toString() + "\n" + getBundleString(intent.getExtras());
|
|
}
|
|
|
|
public static String getBundleString(Bundle bundle) {
|
|
if (bundle == null || bundle.size() == 0) return "Bundle[]";
|
|
|
|
StringBuilder bundleString = new StringBuilder("Bundle[\n");
|
|
boolean first = true;
|
|
for (String key : bundle.keySet()) {
|
|
if (!first)
|
|
bundleString.append("\n");
|
|
|
|
bundleString.append(key).append(": `");
|
|
|
|
Object value = bundle.get(key);
|
|
if (value instanceof int[]) {
|
|
bundleString.append(Arrays.toString((int[]) value));
|
|
} else if (value instanceof byte[]) {
|
|
bundleString.append(Arrays.toString((byte[]) value));
|
|
} else if (value instanceof boolean[]) {
|
|
bundleString.append(Arrays.toString((boolean[]) value));
|
|
} else if (value instanceof short[]) {
|
|
bundleString.append(Arrays.toString((short[]) value));
|
|
} else if (value instanceof long[]) {
|
|
bundleString.append(Arrays.toString((long[]) value));
|
|
} else if (value instanceof float[]) {
|
|
bundleString.append(Arrays.toString((float[]) value));
|
|
} else if (value instanceof double[]) {
|
|
bundleString.append(Arrays.toString((double[]) value));
|
|
} else if (value instanceof String[]) {
|
|
bundleString.append(Arrays.toString((String[]) value));
|
|
} else if (value instanceof CharSequence[]) {
|
|
bundleString.append(Arrays.toString((CharSequence[]) value));
|
|
} else if (value instanceof Parcelable[]) {
|
|
bundleString.append(Arrays.toString((Parcelable[]) value));
|
|
} else if (value instanceof Bundle) {
|
|
bundleString.append(getBundleString((Bundle) value));
|
|
} else {
|
|
bundleString.append(value);
|
|
}
|
|
|
|
bundleString.append("`");
|
|
|
|
first = false;
|
|
}
|
|
|
|
bundleString.append("\n]");
|
|
return bundleString.toString();
|
|
}
|
|
|
|
}
|