From f91168eff44d528e82588b7e6d9f786a1ba0a1bc Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Sep 2020 22:20:23 +0500 Subject: [PATCH 1/5] Fix issue where termux crashes occasionally in android >= 8 because "startForeground()" function is not being called before running "startForegroundService()" in RunCommandService. --- .../com/termux/app/RunCommandService.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/app/src/main/java/com/termux/app/RunCommandService.java b/app/src/main/java/com/termux/app/RunCommandService.java index 2a57864d..d4af178b 100644 --- a/app/src/main/java/com/termux/app/RunCommandService.java +++ b/app/src/main/java/com/termux/app/RunCommandService.java @@ -1,6 +1,10 @@ package com.termux.app; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Binder; @@ -8,6 +12,8 @@ import android.os.Build; import android.os.IBinder; import android.util.Log; +import com.termux.R; + import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; @@ -50,6 +56,9 @@ public class RunCommandService extends Service { public static final String RUN_COMMAND_WORKDIR = "com.termux.RUN_COMMAND_WORKDIR"; public static final String RUN_COMMAND_BACKGROUND = "com.termux.RUN_COMMAND_BACKGROUND"; + private static final String NOTIFICATION_CHANNEL_ID = "termux_run_command_notification_channel"; + private static final int NOTIFICATION_ID = 1338; + class LocalBinder extends Binder { public final RunCommandService service = RunCommandService.this; } @@ -61,7 +70,15 @@ public class RunCommandService extends Service { return mBinder; } + @Override + public void onCreate() { + runStartForeground(); + } + public int onStartCommand(Intent intent, int flags, int startId) { + // Run again in case service is already started and onCreate() is not called + runStartForeground(); + if (allowExternalApps() && RUN_COMMAND_ACTION.equals(intent.getAction())) { Uri programUri = new Uri.Builder().scheme("com.termux.file").path(intent.getStringExtra(RUN_COMMAND_PATH)).build(); @@ -78,9 +95,56 @@ public class RunCommandService extends Service { } } + runStopForeground(); + return Service.START_NOT_STICKY; } + private void runStartForeground() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + setupNotificationChannel(); + startForeground(NOTIFICATION_ID, buildNotification()); + } + } + + private void runStopForeground() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + stopForeground(true); + } + } + + private Notification buildNotification() { + Notification.Builder builder = new Notification.Builder(this); + builder.setContentTitle(getText(R.string.application_name) + " Run Command"); + builder.setSmallIcon(R.drawable.ic_service_notification); + + // Use a low priority: + builder.setPriority(Notification.PRIORITY_LOW); + + // No need to show a timestamp: + builder.setShowWhen(false); + + // Background color for small notification icon: + builder.setColor(0xFF607D8B); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setChannelId(NOTIFICATION_CHANNEL_ID); + } + + return builder.build(); + } + + private void setupNotificationChannel() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; + + String channelName = "Termux Run Command"; + int importance = NotificationManager.IMPORTANCE_LOW; + + NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, importance); + NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + manager.createNotificationChannel(channel); + } + private boolean allowExternalApps() { File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties"); if (!propsFile.exists()) From 52ce6cc94bbe59dc8d95cae00e83a2c663963ee3 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Sep 2020 22:21:31 +0500 Subject: [PATCH 2/5] Add support for "$PREFIX/" and "~/" prefix in "RUN_COMMAND" intent extras for paths. --- .../java/com/termux/app/RunCommandService.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/app/RunCommandService.java b/app/src/main/java/com/termux/app/RunCommandService.java index d4af178b..e0e9b55d 100644 --- a/app/src/main/java/com/termux/app/RunCommandService.java +++ b/app/src/main/java/com/termux/app/RunCommandService.java @@ -80,12 +80,12 @@ public class RunCommandService extends Service { runStartForeground(); if (allowExternalApps() && RUN_COMMAND_ACTION.equals(intent.getAction())) { - Uri programUri = new Uri.Builder().scheme("com.termux.file").path(intent.getStringExtra(RUN_COMMAND_PATH)).build(); + Uri programUri = new Uri.Builder().scheme("com.termux.file").path(parsePath(intent.getStringExtra(RUN_COMMAND_PATH))).build(); Intent execIntent = new Intent(TermuxService.ACTION_EXECUTE, programUri); execIntent.setClass(this, TermuxService.class); execIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, intent.getStringArrayExtra(RUN_COMMAND_ARGUMENTS)); - execIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, intent.getStringExtra(RUN_COMMAND_WORKDIR)); + execIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, parsePath(intent.getStringExtra(RUN_COMMAND_WORKDIR))); execIntent.putExtra(TermuxService.EXTRA_EXECUTE_IN_BACKGROUND, intent.getBooleanExtra(RUN_COMMAND_BACKGROUND, false)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -163,4 +163,14 @@ public class RunCommandService extends Service { return props.getProperty("allow-external-apps", "false").equals("true"); } + + /** Replace "$PREFIX/" or "~/" prefix with termux absolute paths */ + private String parsePath(String path) { + if(path != null && !path.isEmpty()) { + path = path.replaceAll("^\\$PREFIX\\/", TermuxService.PREFIX_PATH + "/"); + path = path.replaceAll("^~\\/", TermuxService.HOME_PATH + "/"); + } + + return path; + } } From 9aae665bfca0ea401ef4582165c3686d347ef9d7 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Sep 2020 22:21:58 +0500 Subject: [PATCH 3/5] Fix issue where termux session does not come to foreground automatically in android >= 10 unless user manually clicks termux notification for "RUN_COMMAND" intents and "Termux:Tasker" plugin actions that have background mode "false" because of new restrictions of starting activities from background. This is done by adding "android.permission.SYSTEM_ALERT_WINDOW" permission in AndroidManifest.xml so that the user may optionally grant "Draw Over Apps" permission to termux to fix the issue. --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b2930376..23475866 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + Date: Fri, 18 Sep 2020 22:22:35 +0500 Subject: [PATCH 4/5] Update RunCommandService docs. --- .../java/com/termux/app/RunCommandService.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/app/RunCommandService.java b/app/src/main/java/com/termux/app/RunCommandService.java index e0e9b55d..4ade0fdc 100644 --- a/app/src/main/java/com/termux/app/RunCommandService.java +++ b/app/src/main/java/com/termux/app/RunCommandService.java @@ -26,9 +26,21 @@ import java.util.Properties; * * Third-party program must declare com.termux.permission.RUN_COMMAND permission and it should be * granted by user. - * Full path of command or script must be given in "RUN_COMMAND_PATH" extra. + * + * Absolute path of command or script must be given in "RUN_COMMAND_PATH" extra. * The "RUN_COMMAND_ARGUMENTS", "RUN_COMMAND_WORKDIR" and "RUN_COMMAND_BACKGROUND" extras are - * optional. The background mode defaults to false. + * optional. The workdir defaults to termux home. The background mode defaults to "false". + * The command path and workdir can optionally be prefixed with "$PREFIX/" or "~/" if an absolute + * path is not to be given. + * + * To automatically bring to foreground and start termux commands that were started with + * background mode "false" in android >= 10 without user having to click the notification manually, + * requires termux to be granted draw over apps permission due to new restrictions + * of starting activities from the background, this also applies to Termux:Tasker plugin. + * + * To reduce the chance of termux being killed by android even further due to violation of not + * being able to call startForeground() within ~5s of service start in android >= 8, the user + * may disable battery optimizations for termux. * * Sample code to run command "top" with java: * Intent intent = new Intent(); From 7063a3a9da3cc1534c190b79ecf40b144fd6663f Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Sep 2020 22:25:05 +0500 Subject: [PATCH 5/5] Add @override annotation to onStartCommand() function of RunCommandService. --- app/src/main/java/com/termux/app/RunCommandService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/termux/app/RunCommandService.java b/app/src/main/java/com/termux/app/RunCommandService.java index 4ade0fdc..8079091a 100644 --- a/app/src/main/java/com/termux/app/RunCommandService.java +++ b/app/src/main/java/com/termux/app/RunCommandService.java @@ -87,6 +87,7 @@ public class RunCommandService extends Service { runStartForeground(); } + @Override public int onStartCommand(Intent intent, int flags, int startId) { // Run again in case service is already started and onCreate() is not called runStartForeground();