diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index a49da8ca2d..f4ef07caf2 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -25,6 +25,7 @@ _scrcpy() { --display-id= --display-ime-policy= --display-orientation= + --do-not-disturb -e --select-tcpip -f --fullscreen --force-adb-forward diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 04ffb8f173..c46b20b991 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -32,6 +32,7 @@ arguments=( '--display-id=[Specify the display id to mirror]' '--display-ime-policy[Set the policy for selecting where the IME should be displayed]' '--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)' + '--do-not-disturb[Enable Do Not Disturb]' {-e,--select-tcpip}'[Use TCP/IP device]' {-f,--fullscreen}'[Start in fullscreen]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' diff --git a/app/src/cli.c b/app/src/cli.c index b2e3e30a53..d69440675b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -114,6 +114,7 @@ enum { OPT_NO_VD_SYSTEM_DECORATIONS, OPT_NO_VD_DESTROY_CONTENT, OPT_DISPLAY_IME_POLICY, + OPT_DO_NOT_DISTURB, }; struct sc_option { @@ -409,6 +410,11 @@ static const struct sc_option options[] = { "before the rotation.\n" "Default is 0.", }, + { + .longopt_id = OPT_DO_NOT_DISTURB, + .longopt = "do-not-disturb", + .text = "Enable Do Not Disturb", + }, { .shortopt = 'e', .longopt = "select-tcpip", @@ -2821,6 +2827,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_DO_NOT_DISTURB: + opts->do_not_disturb = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 0fe82d291b..11553a6496 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -113,6 +113,7 @@ const struct scrcpy_options scrcpy_options_default = { .angle = NULL, .vd_destroy_content = true, .vd_system_decorations = true, + .do_not_disturb = false, }; enum sc_orientation diff --git a/app/src/options.h b/app/src/options.h index 03b4291344..90624e404b 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -314,6 +314,7 @@ struct scrcpy_options { bool require_audio; bool kill_adb_on_close; bool camera_high_speed; + bool do_not_disturb; #define SC_OPTION_LIST_ENCODERS 0x1 #define SC_OPTION_LIST_DISPLAYS 0x2 #define SC_OPTION_LIST_CAMERAS 0x4 diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index aedfdf9cf8..d2c5f53541 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -453,6 +453,7 @@ scrcpy(struct scrcpy_options *options) { .audio_dup = options->audio_dup, .show_touches = options->show_touches, .stay_awake = options->stay_awake, + .do_not_disturb = options->do_not_disturb, .video_codec_options = options->video_codec_options, .audio_codec_options = options->audio_codec_options, .video_encoder = options->video_encoder, diff --git a/app/src/server.c b/app/src/server.c index 153219c3f4..cf1e45ba8d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -360,6 +360,9 @@ execute_server(struct sc_server *server, if (params->show_touches) { ADD_PARAM("show_touches=true"); } + if (params->do_not_disturb) { + ADD_PARAM("do_not_disturb=true"); + } if (params->stay_awake) { ADD_PARAM("stay_awake=true"); } diff --git a/app/src/server.h b/app/src/server.h index 5f4592de90..ea42245725 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -56,6 +56,7 @@ struct sc_server_params { bool audio_dup; bool show_touches; bool stay_awake; + bool do_not_disturb; bool force_adb_forward; bool power_off_on_close; bool clipboard_autosync; diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index a5816c32a1..89bc9095e2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -6,6 +6,7 @@ import com.genymobile.scrcpy.util.SettingsException; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.app.NotificationManager; import android.os.BatteryManager; import android.os.Looper; import android.system.ErrnoException; @@ -80,6 +81,18 @@ private void runCleanUp(Options options) { } } + int previousDoNotDisturbMode = -1; + if (options.getDoNotDisturb()) { + try { + NotificationManager notificationManager = ServiceManager.getNotificationManager(); + previousDoNotDisturbMode = notificationManager.getCurrentInterruptionFilter(); + notificationManager.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS); + + } catch (Exception e) { + Ln.e("Could not set dnd mode", e); + } + } + int restoreScreenOffTimeout = -1; int screenOffTimeout = options.getScreenOffTimeout(); if (screenOffTimeout != -1) { @@ -116,14 +129,15 @@ private void runCleanUp(Options options) { boolean powerOffScreen = options.getPowerOffScreenOnClose(); try { - run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout, restoreDisplayImePolicy); + run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout, restoreDisplayImePolicy, previousDoNotDisturbMode); } catch (IOException e) { Ln.e("Clean up I/O exception", e); } } private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout, - int restoreDisplayImePolicy) throws IOException { + int restoreDisplayImePolicy, + int previousDoNotDisturbMode) throws IOException { String[] cmd = { "app_process", "/", @@ -134,6 +148,7 @@ private void run(int displayId, int restoreStayOn, boolean disableShowTouches, b String.valueOf(powerOffScreen), String.valueOf(restoreScreenOffTimeout), String.valueOf(restoreDisplayImePolicy), + String.valueOf(previousDoNotDisturbMode), }; ProcessBuilder builder = new ProcessBuilder(cmd); @@ -204,6 +219,7 @@ public static void main(String... args) { boolean powerOffScreen = Boolean.parseBoolean(args[3]); int restoreScreenOffTimeout = Integer.parseInt(args[4]); int restoreDisplayImePolicy = Integer.parseInt(args[5]); + int previousDoNotDisturbMode = Integer.parseInt(args[6]); // Dynamic option boolean restoreDisplayPower = false; @@ -266,6 +282,11 @@ public static void main(String... args) { } } + if(previousDoNotDisturbMode != -1) { + Ln.i("Restoring DoNotDisturbMode"); + ServiceManager.getNotificationManager().setInterruptionFilter(previousDoNotDisturbMode); + } + System.exit(0); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 66bb68e8a9..c7520a5cdf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -48,6 +48,7 @@ public class Options { private boolean cameraHighSpeed; private boolean showTouches; private boolean stayAwake; + private boolean doNotDisturb; private int screenOffTimeout = -1; private int displayImePolicy = -1; private List videoCodecOptions; @@ -184,6 +185,10 @@ public boolean getStayAwake() { return stayAwake; } + public boolean getDoNotDisturb() { + return doNotDisturb; + } + public int getScreenOffTimeout() { return screenOffTimeout; } @@ -393,6 +398,9 @@ public static Options parse(String... args) { case "stay_awake": options.stayAwake = Boolean.parseBoolean(value); break; + case "do_not_disturb": + options.doNotDisturb = Boolean.parseBoolean(value); + break; case "screen_off_timeout": options.screenOffTimeout = Integer.parseInt(value); if (options.screenOffTimeout < -1) { diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index b1123b55fc..246b321b32 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -2,6 +2,7 @@ import com.genymobile.scrcpy.FakeContext; +import android.app.NotificationManager; import android.annotation.SuppressLint; import android.content.Context; import android.hardware.camera2.CameraManager; @@ -32,6 +33,7 @@ public final class ServiceManager { private static ClipboardManager clipboardManager; private static ActivityManager activityManager; private static CameraManager cameraManager; + private static NotificationManager notificationManager; private ServiceManager() { /* not instantiable */ @@ -109,4 +111,17 @@ public static CameraManager getCameraManager() { } return cameraManager; } + + public static NotificationManager getNotificationManager() { + if (notificationManager == null) { + try { + // a similar method like CameraManager is only available for Android >= 16 + // https://android.googlesource.com/platform/frameworks/base/+blame/refs/tags/android-16.0.0_r4/core/java/android/app/NotificationManager.java#718 + notificationManager = (NotificationManager) FakeContext.get().getSystemService(Context.NOTIFICATION_SERVICE); + } catch (Exception e) { + throw new AssertionError(e); + } + } + return notificationManager; + } }