Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce --root option for (rooted) secure display support on Android 12+ #4127

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from
1 change: 1 addition & 0 deletions app/data/bash-completion/scrcpy
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ _scrcpy() {
--record-orientation=
--render-driver=
--require-audio
--root
--rotation=
-s --serial=
-S --turn-screen-off
Expand Down
2 changes: 2 additions & 0 deletions app/data/zsh-completion/_scrcpy
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ arguments=(
'--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)'
'--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]'
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
Expand Down
8 changes: 8 additions & 0 deletions app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ enum {
OPT_DISPLAY_ORIENTATION,
OPT_RECORD_ORIENTATION,
OPT_ORIENTATION,
OPT_ROOT,
};

struct sc_option {
Expand Down Expand Up @@ -650,6 +651,11 @@ static const struct sc_option options[] = {
.longopt = "rotation",
.argdesc = "value",
},
{
.longopt_id = OPT_ROOT,
.longopt = "root",
.text = "Launch the server as root (disabled by default).",
},
{
.shortopt = 's',
.longopt = "serial",
Expand Down Expand Up @@ -2354,6 +2360,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
break;
case OPT_CAMERA_HIGH_SPEED:
opts->camera_high_speed = true;
case OPT_ROOT:
opts->root = true;
break;
default:
// getopt prints the error message on stderr
Expand Down
1 change: 1 addition & 0 deletions app/src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const struct scrcpy_options scrcpy_options_default = {
.kill_adb_on_close = false,
.camera_high_speed = false,
.list = 0,
.root = false,
};

enum sc_orientation
Expand Down
1 change: 1 addition & 0 deletions app/src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ struct scrcpy_options {
#define SC_OPTION_LIST_CAMERAS 0x4
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
uint8_t list;
bool root;
};

extern const struct scrcpy_options scrcpy_options_default;
Expand Down
1 change: 1 addition & 0 deletions app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ scrcpy(struct scrcpy_options *options) {
.kill_adb_on_close = options->kill_adb_on_close,
.camera_high_speed = options->camera_high_speed,
.list = options->list,
.root = options->root,
};

static const struct sc_server_callbacks cbs = {
Expand Down
7 changes: 7 additions & 0 deletions app/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,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";

Expand Down
1 change: 1 addition & 0 deletions app/src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ struct sc_server_params {
bool kill_adb_on_close;
bool camera_high_speed;
uint8_t list;
bool root;
};

struct sc_server {
Expand Down
4 changes: 2 additions & 2 deletions server/src/main/java/com/genymobile/scrcpy/AudioCapture.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ private static void startWorkaroundAndroid11() {
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 {
Expand Down
3 changes: 2 additions & 1 deletion server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.system.Os;

import java.io.IOException;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -167,7 +168,7 @@ private synchronized void waitEnded() {

@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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.media.MediaCodec;
import android.os.Build;
import android.system.Os;

import java.io.IOException;
import java.nio.ByteBuffer;
Expand All @@ -19,7 +20,7 @@ public AudioRawRecorder(AudioCapture capture, Streamer streamer) {
}

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;
Expand Down
5 changes: 4 additions & 1 deletion server/src/main/java/com/genymobile/scrcpy/FakeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import android.content.ContextWrapper;
import android.os.Build;
import android.os.Process;
import android.system.Os;

public final class FakeContext extends ContextWrapper {

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();
Expand All @@ -32,6 +34,7 @@ public String getOpPackageName() {
return PACKAGE_NAME;
}


@TargetApi(Build.VERSION_CODES.S)
@Override
public AttributionSource getAttributionSource() {
Expand Down
7 changes: 5 additions & 2 deletions server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ public void onRotationChanged(int rotation) {
private static IBinder createDisplay() {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// 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));
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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.os.Looper;
import android.os.SystemClock;
import android.view.Surface;
import android.system.Os;

import java.io.IOException;
import java.nio.ByteBuffer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private static ClipData getPrimaryClip(Method method, int methodVersion, IInterf
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null);
default:
// The last boolean parameter is "userOperate"
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true);
return (ClipData) method.invoke(manager, FakeContext.FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true);
}
}

Expand Down