diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 003f9d73..db280379 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -53,6 +53,7 @@ _scrcpy() { --record-format= --render-driver= --require-audio + --root --rotation= -s --serial= -S --turn-screen-off diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 81142851..0cb0a449 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -59,6 +59,7 @@ arguments=( '--record-format=[Force recording format]:format:(mp4 mkv)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' + '--root[Launch the server as root]' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))' {-S,--turn-screen-off}'[Turn the device screen off immediately]' diff --git a/app/src/cli.c b/app/src/cli.c index 37a98426..ad50b088 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -79,6 +79,7 @@ enum { OPT_AUDIO_SOURCE, OPT_KILL_ADB_ON_CLOSE, OPT_TIME_LIMIT, + OPT_ROOT, }; struct sc_option { @@ -535,6 +536,11 @@ static const struct sc_option options[] = { "Possible values are 0, 1, 2 and 3. Each increment adds a 90 " "degrees rotation counterclockwise.", }, + { + .longopt_id = OPT_ROOT, + .longopt = "root", + .text = "Launch the server as root (disabled by default).", + }, { .shortopt = 's', .longopt = "serial", @@ -1977,6 +1983,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_ROOT: + opts->root = 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 530e003b..563c1222 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -82,4 +82,5 @@ const struct scrcpy_options scrcpy_options_default = { .list_encoders = false, .list_displays = false, .kill_adb_on_close = false, + .root = false, }; diff --git a/app/src/options.h b/app/src/options.h index 1f36ad7f..942114b4 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -182,6 +182,7 @@ struct scrcpy_options { bool list_encoders; bool list_displays; bool kill_adb_on_close; + bool root; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index fd310c46..b7d3e7f1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -380,6 +380,7 @@ scrcpy(struct scrcpy_options *options) { .list_encoders = options->list_encoders, .list_displays = options->list_displays, .kill_adb_on_close = options->kill_adb_on_close, + .root = options->root, }; static const struct sc_server_callbacks cbs = { diff --git a/app/src/server.c b/app/src/server.c index 4d787ea9..e52b4965 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -194,6 +194,13 @@ execute_server(struct sc_server *server, cmd[count++] = "-s"; cmd[count++] = serial; cmd[count++] = "shell"; + + if (params->root) { + cmd[count++] = "su"; + cmd[count++] = "1000"; // AID_SYSTEM, AID_GRAPHICS is also supported for FLAG_SECURE but lacks other perms + cmd[count++] = "-c"; + } + cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH; cmd[count++] = "app_process"; diff --git a/app/src/server.h b/app/src/server.h index adba2652..5c5a8e8d 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -59,6 +59,7 @@ struct sc_server_params { bool list_encoders; bool list_displays; bool kill_adb_on_close; + bool root; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 5575ffb6..af69e8a6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -74,12 +74,12 @@ public final class AudioCapture { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); + intent.setComponent(new ComponentName(FakeContext.PACKAGE_SHELL, "com.android.shell.HeapDumpActivity")); ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); } private static void stopWorkaroundAndroid11() { - ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME); + ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_SHELL); } private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureForegroundException { diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index bec79b05..5424e28a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -7,6 +7,7 @@ import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; +import android.system.Os; import java.io.IOException; import java.nio.ByteBuffer; @@ -170,7 +171,7 @@ public final class AudioEncoder implements AsyncProcessor { @TargetApi(Build.VERSION_CODES.M) public void encode() throws IOException, ConfigurationException, AudioCaptureForegroundException { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + if ((Os.getuid() == 2000) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)) { Ln.w("Audio disabled: it is not supported before Android 11"); streamer.writeDisableStream(false); return; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index 7d2adade..9a35dbdb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy; import android.media.MediaCodec; import android.os.Build; +import android.system.Os; import java.io.IOException; import java.nio.ByteBuffer; @@ -22,7 +23,7 @@ public final class AudioRawRecorder implements AsyncProcessor { } private void record() throws IOException, AudioCaptureForegroundException { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + if ((Os.getuid() == 2000) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)) { Ln.w("Audio disabled: it is not supported before Android 11"); streamer.writeDisableStream(false); return; diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 6501d4cf..1d9c4a56 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -5,10 +5,12 @@ import android.content.AttributionSource; import android.content.MutableContextWrapper; import android.os.Build; import android.os.Process; +import android.system.Os; public final class FakeContext extends MutableContextWrapper { - public static final String PACKAGE_NAME = "com.android.shell"; + public static final String PACKAGE_NAME = Os.getuid() == 1000 ? "android" : "com.android.shell"; + public static final String PACKAGE_SHELL = "com.android.shell"; public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29 private static final FakeContext INSTANCE = new FakeContext(); @@ -31,6 +33,7 @@ public final class FakeContext extends MutableContextWrapper { return PACKAGE_NAME; } + @TargetApi(Build.VERSION_CODES.S) @Override public AttributionSource getAttributionSource() { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 5a9db10d..bdbbfe07 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -11,6 +11,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.SystemClock; import android.view.Surface; +import android.system.Os; import java.io.IOException; import java.nio.ByteBuffer; @@ -269,6 +270,9 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S" .equals(Build.VERSION.CODENAME)); + if (Os.getuid() < 2000) { + secure = true; + } return SurfaceControl.createDisplay("scrcpy", secure); }