From 605dd6c192de882cea8113b2316fb4bca6bc370a Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 6 Jul 2022 02:53:36 +0500 Subject: [PATCH] Added: Add check for if Termux has been granted Display Over Apps Permission if starting activities and services with termux-am-socket on Android 10+ --- .../java/com/termux/app/TermuxService.java | 2 +- app/src/main/res/values/strings.xml | 2 +- .../socket/local/LocalSocketRunConfig.java | 16 +-- .../shared/shell/am/AmSocketServer.java | 27 ++++- .../shell/am/AmSocketServerRunConfig.java | 108 ++++++++++++++++++ .../termux/shell/am/TermuxAmSocketServer.java | 5 +- termux-shared/src/main/res/values/strings.xml | 6 + 7 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServerRunConfig.java diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index d26259e1..8025d0bd 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -723,7 +723,7 @@ public final class TermuxService extends Service implements AppShell.AppShellCli TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(this); if (preferences == null) return; if (preferences.arePluginErrorNotificationsEnabled(false)) - Logger.showToast(this, this.getString(R.string.error_display_over_other_apps_permission_not_granted), true); + Logger.showToast(this, this.getString(R.string.error_display_over_other_apps_permission_not_granted_to_start_terminal), true); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ca4c83a5..642fbb5e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,7 +99,7 @@ - &TERMUX_APP_NAME; requires + &TERMUX_APP_NAME; requires \"Display over other apps\" permission to start terminal sessions from background on Android >= 10. Grants it from Settings -> Apps -> &TERMUX_APP_NAME; -> Advanced Invalid execution command runner to TermuxService: `%1$s` diff --git a/termux-shared/src/main/java/com/termux/shared/net/socket/local/LocalSocketRunConfig.java b/termux-shared/src/main/java/com/termux/shared/net/socket/local/LocalSocketRunConfig.java index 53ef8957..c09f5bef 100644 --- a/termux-shared/src/main/java/com/termux/shared/net/socket/local/LocalSocketRunConfig.java +++ b/termux-shared/src/main/java/com/termux/shared/net/socket/local/LocalSocketRunConfig.java @@ -16,7 +16,7 @@ import java.nio.charset.StandardCharsets; public class LocalSocketRunConfig implements Serializable { /** The {@link LocalSocketManager} title. */ - private final String mTitle; + protected final String mTitle; /** * The {@link LocalServerSocket} path. @@ -40,19 +40,19 @@ public class LocalSocketRunConfig implements Serializable { * * Max allowed length is 108 bytes as per sun_path size (UNIX_PATH_MAX) on Linux. */ - private final String mPath; + protected final String mPath; /** If abstract namespace {@link LocalServerSocket} instead of filesystem. */ protected final boolean mAbstractNamespaceSocket; /** The {@link ILocalSocketManager} client for the {@link LocalSocketManager}. */ - private final ILocalSocketManager mLocalSocketManagerClient; + protected final ILocalSocketManager mLocalSocketManagerClient; /** * The {@link LocalServerSocket} file descriptor. * Value will be `>= 0` if socket has been created successfully and `-1` if not created or closed. */ - private int mFD = -1; + protected int mFD = -1; /** * The {@link LocalClientSocket} receiving (SO_RCVTIMEO) timeout in milliseconds. @@ -61,7 +61,7 @@ public class LocalSocketRunConfig implements Serializable { * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:frameworks/base/services/core/java/com/android/server/am/NativeCrashListener.java;l=55 * Defaults to {@link #DEFAULT_RECEIVE_TIMEOUT}. */ - private Integer mReceiveTimeout; + protected Integer mReceiveTimeout; public static final int DEFAULT_RECEIVE_TIMEOUT = 10000; /** @@ -71,7 +71,7 @@ public class LocalSocketRunConfig implements Serializable { * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:frameworks/base/services/core/java/com/android/server/am/NativeCrashListener.java;l=55 * Defaults to {@link #DEFAULT_SEND_TIMEOUT}. */ - private Integer mSendTimeout; + protected Integer mSendTimeout; public static final int DEFAULT_SEND_TIMEOUT = 10000; /** @@ -80,7 +80,7 @@ public class LocalSocketRunConfig implements Serializable { * deadline. * Defaults to {@link #DEFAULT_DEADLINE}. */ - private Long mDeadline; + protected Long mDeadline; public static final int DEFAULT_DEADLINE = 0; /** @@ -91,7 +91,7 @@ public class LocalSocketRunConfig implements Serializable { * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:frameworks/base/core/java/android/net/LocalSocketManager.java;l=31 * Defaults to {@link #DEFAULT_BACKLOG}. */ - private Integer mBacklog; + protected Integer mBacklog; public static final int DEFAULT_BACKLOG = 50; diff --git a/termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServer.java b/termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServer.java index f94e3453..e316ec06 100644 --- a/termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServer.java +++ b/termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServer.java @@ -1,5 +1,6 @@ package com.termux.shared.shell.am; +import android.Manifest; import android.app.Application; import android.content.Context; @@ -7,6 +8,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.termux.am.Am; +import com.termux.shared.R; +import com.termux.shared.android.PackageUtils; +import com.termux.shared.android.PermissionUtils; import com.termux.shared.errors.Error; import com.termux.shared.logger.Logger; import com.termux.shared.net.socket.local.ILocalSocketManager; @@ -40,8 +44,9 @@ import java.util.List; * 1. Optionally extend {@link AmSocketServerClient}, the implementation for * {@link ILocalSocketManager} that will receive call backs from the server including * when client connects via {@link ILocalSocketManager#onClientAccepted(LocalSocketManager, LocalClientSocket)}. - * 2. Create a {@link LocalSocketRunConfig} instance with the run config of the am server. It would - * be better to use a filesystem socket instead of abstract namespace socket for security reasons. + * 2. Create a {@link AmSocketServerRunConfig} instance which extends from {@link LocalSocketRunConfig} + * with the run config of the am server. It would be better to use a filesystem socket instead + * of abstract namespace socket for security reasons. * 3. Call {@link #start(Context, LocalSocketRunConfig)} to start the server and store the {@link LocalSocketManager} * instance returned. * 4. Stop server if needed with a call to {@link LocalSocketManager#stop()} on the @@ -104,10 +109,13 @@ public class AmSocketServer { Logger.logDebug(LOG_TAG, "am command received from peer " + clientSocket.getPeerCred().getMinimalString() + "\n" + ExecutionCommand.getArgumentsLogString("am command", amCommandArray)); + AmSocketServerRunConfig amSocketServerRunConfig = (AmSocketServerRunConfig) localSocketManager.getLocalSocketRunConfig(); + // Run am command and send its result to the client StringBuilder stdout = new StringBuilder(); StringBuilder stderr = new StringBuilder(); - error = runAmCommand(localSocketManager.getContext(), amCommandArray, stdout, stderr); + error = runAmCommand(localSocketManager.getContext(), amCommandArray, stdout, stderr, + amSocketServerRunConfig.shouldCheckDisplayOverAppsPermission()); if (error != null) { sendResultToClient(localSocketManager, clientSocket, 1, stdout.toString(), !stderr.toString().isEmpty() ? stderr + "\n\n" + error : error.toString()); @@ -194,16 +202,27 @@ public class AmSocketServer { * @param amCommandArray The am command array. * @param stdout The {@link StringBuilder} to set stdout in that is returned by the am command. * @param stderr The {@link StringBuilder} to set stderr in that is returned by the am command. + * @param checkDisplayOverAppsPermission Check if {@link Manifest.permission#SYSTEM_ALERT_WINDOW} + * has been granted if running on Android `>= 10` and + * starting activity or service. * @return Returns the {@code error} if am command failed, otherwise {@code null}. */ public static Error runAmCommand(@NonNull Context context, String[] amCommandArray, - @NonNull StringBuilder stdout, @NonNull StringBuilder stderr) { + @NonNull StringBuilder stdout, @NonNull StringBuilder stderr, + boolean checkDisplayOverAppsPermission) { try (ByteArrayOutputStream stdoutByteStream = new ByteArrayOutputStream(); PrintStream stdoutPrintStream = new PrintStream(stdoutByteStream); ByteArrayOutputStream stderrByteStream = new ByteArrayOutputStream(); PrintStream stderrPrintStream = new PrintStream(stderrByteStream)) { + if (checkDisplayOverAppsPermission && amCommandArray.length >= 1 && + (amCommandArray[0].equals("start") || amCommandArray[0].equals("startservice")) && + !PermissionUtils.validateDisplayOverOtherAppsPermissionForPostAndroid10(context, true)) { + throw new IllegalStateException(context.getString(R.string.error_display_over_other_apps_permission_not_granted, + PackageUtils.getAppNameForPackage(context))); + } + new Am(stdoutPrintStream, stderrPrintStream, (Application) context.getApplicationContext()).run(amCommandArray); // Set stdout to value set by am command in stdoutPrintStream diff --git a/termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServerRunConfig.java b/termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServerRunConfig.java new file mode 100644 index 00000000..f36cbece --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServerRunConfig.java @@ -0,0 +1,108 @@ +package com.termux.shared.shell.am; + +import android.Manifest; + +import androidx.annotation.NonNull; + +import com.termux.shared.logger.Logger; +import com.termux.shared.markdown.MarkdownUtils; +import com.termux.shared.net.socket.local.ILocalSocketManager; +import com.termux.shared.net.socket.local.LocalSocketRunConfig; + +import java.io.Serializable; + +/** + * Run config for {@link AmSocketServer}. + */ +public class AmSocketServerRunConfig extends LocalSocketRunConfig implements Serializable { + + /** + * Check if {@link Manifest.permission#SYSTEM_ALERT_WINDOW} has been granted if running on Android `>= 10` + * if starting activities. Will also check when starting services in case starting foreground + * service is not allowed. + * + * https://developer.android.com/guide/components/activities/background-starts + */ + private Boolean mCheckDisplayOverAppsPermission; + public static final boolean DEFAULT_CHECK_DISPLAY_OVER_APPS_PERMISSION = true; + + /** + * Create an new instance of {@link AmSocketServerRunConfig}. + * + * @param title The {@link #mTitle} value. + * @param path The {@link #mPath} value. + * @param localSocketManagerClient The {@link #mLocalSocketManagerClient} value. + */ + public AmSocketServerRunConfig(@NonNull String title, @NonNull String path, @NonNull ILocalSocketManager localSocketManagerClient) { + super(title, path, localSocketManagerClient); + } + + + /** Get {@link #mCheckDisplayOverAppsPermission} if set, otherwise {@link #DEFAULT_CHECK_DISPLAY_OVER_APPS_PERMISSION}. */ + public boolean shouldCheckDisplayOverAppsPermission() { + return mCheckDisplayOverAppsPermission != null ? mCheckDisplayOverAppsPermission : DEFAULT_CHECK_DISPLAY_OVER_APPS_PERMISSION; + } + + /** Set {@link #mCheckDisplayOverAppsPermission}. */ + public void setCheckDisplayOverAppsPermission(Boolean checkDisplayOverAppsPermission) { + mCheckDisplayOverAppsPermission = checkDisplayOverAppsPermission; + } + + + + /** + * Get a log {@link String} for {@link AmSocketServerRunConfig}. + * + * @param config The {@link AmSocketServerRunConfig} to get info of. + * @return Returns the log {@link String}. + */ + @NonNull + public static String getRunConfigLogString(final AmSocketServerRunConfig config) { + if (config == null) return "null"; + return config.getLogString(); + } + + /** Get a log {@link String} for the {@link AmSocketServerRunConfig}. */ + @NonNull + public String getLogString() { + StringBuilder logString = new StringBuilder(); + logString.append(super.getLogString()).append("\n\n\n"); + + logString.append("Am Command:"); + logString.append("\n").append(Logger.getSingleLineLogStringEntry("CheckDisplayOverAppsPermission", shouldCheckDisplayOverAppsPermission(), "-")); + + return logString.toString(); + } + + /** + * Get a markdown {@link String} for {@link AmSocketServerRunConfig}. + * + * @param config The {@link AmSocketServerRunConfig} to get info of. + * @return Returns the markdown {@link String}. + */ + public static String getRunConfigMarkdownString(final AmSocketServerRunConfig config) { + if (config == null) return "null"; + return config.getMarkdownString(); + } + + /** Get a markdown {@link String} for the {@link AmSocketServerRunConfig}. */ + @NonNull + public String getMarkdownString() { + StringBuilder markdownString = new StringBuilder(); + markdownString.append(super.getMarkdownString()).append("\n\n\n"); + + markdownString.append("## ").append("Am Command"); + markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("CheckDisplayOverAppsPermission", shouldCheckDisplayOverAppsPermission(), "-")); + + return markdownString.toString(); + } + + + + @NonNull + @Override + public String toString() { + return getLogString(); + } + +} diff --git a/termux-shared/src/main/java/com/termux/shared/termux/shell/am/TermuxAmSocketServer.java b/termux-shared/src/main/java/com/termux/shared/termux/shell/am/TermuxAmSocketServer.java index 438744e1..0d68ae5c 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/shell/am/TermuxAmSocketServer.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/shell/am/TermuxAmSocketServer.java @@ -13,6 +13,7 @@ import com.termux.shared.net.socket.local.LocalServerSocket; import com.termux.shared.net.socket.local.LocalSocketManager; import com.termux.shared.net.socket.local.LocalSocketManagerClientBase; import com.termux.shared.net.socket.local.LocalSocketRunConfig; +import com.termux.shared.shell.am.AmSocketServerRunConfig; import com.termux.shared.shell.am.AmSocketServer; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.crash.TermuxCrashUtils; @@ -101,10 +102,10 @@ public class TermuxAmSocketServer { public static synchronized void start(@NonNull Context context) { stop(); - LocalSocketRunConfig localSocketRunConfig = new LocalSocketRunConfig(TITLE, + AmSocketServerRunConfig amSocketServerRunConfig = new AmSocketServerRunConfig(TITLE, TermuxConstants.TERMUX_APP.TERMUX_AM_SOCKET_FILE_PATH, new TermuxAmSocketServerClient()); - termuxAmSocketServer = AmSocketServer.start(context, localSocketRunConfig); + termuxAmSocketServer = AmSocketServer.start(context, amSocketServerRunConfig); } /** diff --git a/termux-shared/src/main/res/values/strings.xml b/termux-shared/src/main/res/values/strings.xml index 9124a889..88f555b9 100644 --- a/termux-shared/src/main/res/values/strings.xml +++ b/termux-shared/src/main/res/values/strings.xml @@ -37,6 +37,12 @@ Attempted to ask for permissions that have not been requested in app manifest: %1$s The \"%1$s\" package is targeting targetSdkVersion %2$d and is running on android sdk %3$d but has not set requestLegacyExternalStorage to true in app manifest Requires `DUMP` and `PACKAGE_USAGE_STATS` permission + %1$s requires + \"Display over other apps\" permission to start activities and services from background on Android >= 10. + Grants it from Android Settings -> Apps -> %1$s -> Advanced -> Draw over other apps. + The permission name may be different on different devices, like on Xiaomi, its called + \"Display pop-up windows while running in the background\", check https://dontkillmyapp.com + for device specific issues.