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+

This commit is contained in:
agnostic-apollo
2022-07-06 02:53:36 +05:00
parent 4646aca597
commit 605dd6c192
7 changed files with 150 additions and 16 deletions

View File

@@ -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);
}
}

View File

@@ -99,7 +99,7 @@
<!-- TermuxService -->
<string name="error_display_over_other_apps_permission_not_granted">&TERMUX_APP_NAME; requires
<string name="error_display_over_other_apps_permission_not_granted_to_start_terminal">&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</string>
<string name="error_termux_service_invalid_execution_command_runner">Invalid execution command runner to TermuxService: `%1$s`</string>

View File

@@ -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;

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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);
}
/**

View File

@@ -37,6 +37,12 @@
<string name="error_attempted_to_ask_for_permissions_not_requested">Attempted to ask for permissions that have not been requested in app manifest: %1$s</string>
<string name="error_has_not_requested_legacy_external_storage">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</string>
<string name="msg_requires_dump_and_package_usage_stats_permissions">Requires `DUMP` and `PACKAGE_USAGE_STATS` permission</string>
<string name="error_display_over_other_apps_permission_not_granted">%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.</string>