Added: Write termux shell environment to /data/data/com.termux/files/usr/etc/termux/termux.env on app startup and package changes

The `termux.env` can be sourced by shells to set termux environment normally exported. This can be useful for users starting termux shells with `adb` `run-as` or `root`. The file will not contain `SHELL_CMD__` variables since those are shell command specific.

The items in the `termux.env` file have the format `export name="value"`.
The `"`\$` characters will be escaped with `a backslash `\`, like `\"` if characters are for literal value. Note that if `$` is escaped and if its part of variable, then variable expansion will not happen if `.env` file is sourced. The `\` at the end of a value line means line continuation. Value can contain newline characters.

The `termux.env` file should be sourceable by `POSIX` compliant shells like `bash`, `zsh`, `sh`, android's `mksh`, etc. Other shells with require manual parsing of the file to export variables.

Related discussion #2565
This commit is contained in:
agnostic-apollo
2022-06-12 00:51:19 +05:00
parent f76c20d036
commit 03e1d14e1e
8 changed files with 196 additions and 5 deletions

View File

@@ -12,6 +12,7 @@ import com.termux.shared.termux.crash.TermuxCrashUtils;
import com.termux.shared.termux.file.TermuxFileUtils;
import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences;
import com.termux.shared.termux.settings.properties.TermuxAppSharedProperties;
import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment;
import com.termux.shared.termux.shell.am.TermuxAmSocketServer;
import com.termux.shared.termux.shell.TermuxShellManager;
import com.termux.shared.termux.theme.TermuxThemeUtils;
@@ -48,9 +49,8 @@ public class TermuxApplication extends Application {
// Check and create termux files directory. If failed to access it like in case of secondary
// user or external sd card installation, then don't run files directory related code
Error error = TermuxFileUtils.isTermuxFilesDirectoryAccessible(this, true, true);
if (error != null) {
Logger.logErrorExtended(LOG_TAG, "Termux files directory is not accessible\n" + error);
} else {
boolean isTermuxFilesDirectoryAccessible = error == null;
if (isTermuxFilesDirectoryAccessible) {
Logger.logInfo(LOG_TAG, "Termux files directory is accessible");
error = TermuxFileUtils.isAppsTermuxAppDirectoryAccessible(true, true);
@@ -59,10 +59,17 @@ public class TermuxApplication extends Application {
return;
}
// Setup termux-am-socket server
TermuxAmSocketServer.setupTermuxAmSocketServer(context);
} else {
Logger.logErrorExtended(LOG_TAG, "Termux files directory is not accessible\n" + error);
}
// Init TermuxShellEnvironment constants and caches after everything has been setup including termux-am-socket server
TermuxShellEnvironment.init(this);
if (isTermuxFilesDirectoryAccessible) {
TermuxShellEnvironment.writeEnvironmentToFile(this);
}
}

View File

@@ -19,6 +19,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.termux.R;
import com.termux.app.event.SystemEventReceiver;
import com.termux.app.terminal.TermuxTerminalSessionClient;
import com.termux.shared.termux.plugins.TermuxPluginUtils;
import com.termux.shared.data.IntentUtils;
@@ -116,6 +117,8 @@ public final class TermuxService extends Service implements AppShell.AppShellCli
mShellManager = TermuxShellManager.getShellManager();
runStartForeground();
SystemEventReceiver.registerPackageUpdateEvents(this);
}
@SuppressLint("Wakelock")
@@ -172,6 +175,9 @@ public final class TermuxService extends Service implements AppShell.AppShellCli
killAllTermuxExecutionCommands();
TermuxShellManager.onAppExit(this);
SystemEventReceiver.unregisterPackageUpdateEvents(this);
runStopForeground();
}

View File

@@ -2,12 +2,18 @@ package com.termux.app.event;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.termux.shared.data.IntentUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.termux.TermuxUtils;
import com.termux.shared.termux.file.TermuxFileUtils;
import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment;
import com.termux.shared.termux.shell.TermuxShellManager;
public class SystemEventReceiver extends BroadcastReceiver {
@@ -35,6 +41,11 @@ public class SystemEventReceiver extends BroadcastReceiver {
case Intent.ACTION_BOOT_COMPLETED:
onActionBootCompleted(context, intent);
break;
case Intent.ACTION_PACKAGE_ADDED:
case Intent.ACTION_PACKAGE_REMOVED:
case Intent.ACTION_PACKAGE_REPLACED:
onActionPackageUpdated(context, intent);
break;
default:
Logger.logError(LOG_TAG, "Invalid action \"" + action + "\" passed to " + LOG_TAG);
}
@@ -44,4 +55,37 @@ public class SystemEventReceiver extends BroadcastReceiver {
TermuxShellManager.onActionBootCompleted(context, intent);
}
public synchronized void onActionPackageUpdated(@NonNull Context context, @NonNull Intent intent) {
Uri data = intent.getData();
if (data != null && TermuxUtils.isUriDataForTermuxPluginPackage(data)) {
Logger.logDebug(LOG_TAG, intent.getAction().replaceAll("^android.intent.action.", "") +
" event received for \"" + data.toString().replaceAll("^package:", "") + "\"");
if (TermuxFileUtils.isTermuxFilesDirectoryAccessible(context, false, false) == null)
TermuxShellEnvironment.writeEnvironmentToFile(context);
}
}
/**
* Register {@link SystemEventReceiver} to listen to {@link Intent#ACTION_PACKAGE_ADDED},
* {@link Intent#ACTION_PACKAGE_REMOVED} and {@link Intent#ACTION_PACKAGE_REPLACED} broadcasts.
* They must be registered dynamically and cannot be registered implicitly in
* the AndroidManifest.xml due to Android 8+ restrictions.
*
* https://developer.android.com/guide/components/broadcast-exceptions
*/
public synchronized static void registerPackageUpdateEvents(@NonNull Context context) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
intentFilter.addDataScheme("package");
context.registerReceiver(getInstance(), intentFilter);
}
public synchronized static void unregisterPackageUpdateEvents(@NonNull Context context) {
context.unregisterReceiver(getInstance());
}
}