mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-06 02:35:19 +08:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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>
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user