目录

充电学习中...

X

拆解QtScrcpy的熄屏控制功能

QtScrcpy 是一款优秀的安卓投屏开源软件,其投屏效果接近于直接在PC上使用模拟器,但是本文的关注点在于其一个相对冷门的功能----关闭屏幕

image.png

不同于下面的电源键按钮,关闭屏幕只会熄灭手机屏幕而不会真的锁屏,众所周知手机的屏幕几乎是耗电最高的硬件,那么当设备需要长时间运行时,熄灭屏幕可以延长设备寿命、减少设备散热(更重要的是,屏幕内容将无法被直接看到)。

那么,这是如何实现的呢?起初我以为这是adb原本就支持的功能,但是翻遍了adb的手册也只能找到模拟电源键,即只能实现真锁屏。那就只能去查阅QtScrcpy源码了,顺便也去了解了一下QtScrcpy的工作原理。

QtScrcpy熄屏实现

QtScrcpy实现代码如下

image.png

本质上是调用了安卓android.view.SurfaceControl类的setDisplayPowerMode方法来实现,而调用这个方法,需要ACCESS_SURFACE_FLINGER权限,但权限又只有系统app才能申请,那么QtScrcpy是如何解决的呢?答案就是利用adb权限,QtScrcpy会先将scrcpy-server(在源码中是个安卓项目,有main方法供app_process执行)推送到/data/local/tmp目录(这个目录可以被adb读写),然后使用app_process来执行,完整的命令如下

adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process /com.genymobile.scrcpy.Server com.genymobile.scrcpy.Server 1.24 8000000 false - false

这样就使得后续的PC端控制和视频推流成为了可能。

实现熄屏控制功能

知道了原理之后,我们尝试来拆解这个功能以供自己使用,首先确定权限问题,实现的权限可以有adb和root,由于我的使用场景需要脱离PC并且我的设备都拥有root权限,所以我最后选择用root权限去实现(有线adb拔线时会唤醒点亮屏幕,无线adb需要处于同一局域网并且手机开启wifi adb服务)。

实现入口方法

代码很简单,只需要反射调用android.view.SurfaceControl类的setDisplayPowerMode方法即可,由于使用su或者adb权限,也不需要bypass系统隐藏api,由于最后需要输出为dex来执行,因此使用安卓项目来实现,入口代码如下:

public class MainClass {
    static IBinder binder;

    public static void main(String[] args) {
        int mode = Integer.parseInt(args[0]);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            binder = Reflect.on("android.view.SurfaceControl").call("getBuiltInDisplay",0).get();
        } else {
            binder = Reflect.on("android.view.SurfaceControl").call("getInternalDisplayToken").get();
        }
        Reflect.on("android.view.SurfaceControl").call("setDisplayPowerMode",binder,mode);
    }
}

这样输出的dex文件已经可以满足adb权限场景下的应用了,但是由于我们要脱离PC,因此需要真实的app来执行shell命令。

执行su命令

直接读取入口app的publicSourceDir所在的base.apk供app_process来调用即可

实现代码如下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Context context = getApplicationContext();
        findViewById(R.id.tv1).setOnClickListener(e->{
            //执行shell命令 使用app_process来执行main方法
            CommandUtil.getSingleInstance().exec(String.format("su -c 'CLASSPATH=%s app_process / com.cube.screen.MainClass 0'",context.getApplicationInfo().publicSourceDir));
        });
    }
}
  

效果如下

点击中间文字即可实现熄屏功能,第一次使用时需要给予root权限

image.png


标题:拆解QtScrcpy的熄屏控制功能
作者:Cubeeeee
地址:http://blog.nps.fuguicun.com/articles/2023/02/10/1676006789471.html