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

Bug: after OS update, Honor Magic4 pro can't be mirrored with screen turned off using ScrCpy (closes right away), but can be via Android Studio #4943

Closed
AndroidDeveloperLB opened this issue May 24, 2024 · 25 comments

Comments

@AndroidDeveloperLB
Copy link

AndroidDeveloperLB commented May 24, 2024

  • [V ] I have read the FAQ.
  • [ V] I have searched in existing issues.

Environment

  • OS: [e.g. Debian, Windows, macOS...]
    Windows
  • scrcpy version: [e.g. 1.12.1]
    2.4
  • installation method: [e.g. manual build, apt, snap, brew, Windows release...]
    Extracted the ZIP file.
  • device model:
    [HONOR] HONOR LGE-NX9
  • Android version: [e.g. 10]
    Android 14

Describe the bug
When launching without any parameters and without any key combination, it works fine.
However, when I tell it to also turn off the display (either in parameter or using ALT+O combination), it closes the window of ScrCpy right away.

This is what I got from the console, after I used ALT+O:

PS C:\android\scrcpy> .\scrcpy-console.bat
scrcpy 2.4 <https://github.com/Genymobile/scrcpy>
INFO: ADB device found:
INFO:     -->   (usb)  AJ3D022630001200                device  LGE_NX9
C:\android\scrcpy\scrcpy-server: 1 file pushed, 0 skipped. 82.7 MB/s (69007 bytes in 0.001s)
[server] INFO: Device: [HONOR] HONOR LGE-NX9 (Android 14)
INFO: Renderer: direct3d
INFO: Texture: 1144x2488
Native registration unable to find class 'com/android/server/TrustedUIService'; aborting...
WARN: Device disconnected
Aborted
Press Enter to continue...

And this is what I got on logcat, filtered by errors:

logcat.zip

This issue doesn't occur when using Android Studio to mirror the device and turning off the display.
ScrCpy used to work fine with this device.
The issue started only yesterday, as I've updated the OS.

@eiyooooo
Copy link
Contributor

@eiyooooo
Copy link
Contributor

eiyooooo commented Jun 9, 2024

but can be via Android Studio

It should not work, because Android Studio declared that turn off device display while mirroring is not supported on Android 14.

@AndroidDeveloperLB
Copy link
Author

@eiyooooo They fixed it (maybe using some workaround) but didn't change the text for some reason.

@eiyooooo
Copy link
Contributor

eiyooooo commented Jun 9, 2024

They fixed it (maybe using some workaround) but didn't change the text for some reason.

I tested on my Android 14 phone, and it did not work.

And

https://github.com/JetBrains/android/blob/dbd9aeae0dc5b8c56ce2c7d51208ba26ea0f169b/streaming/screen-sharing-agent/app/src/main/cpp/session_environment.cc#L55-L66

shows that it does not seem to support "turn off device display" on Android 14.

@AndroidDeveloperLB
Copy link
Author

AndroidDeveloperLB commented Jun 9, 2024

@eiyooooo Seems it's as such for me for Android 14 :
Pixel 6 - doesn't turn off in Android Studio, yet ScrCpy can do it.
Honor magic 4 pro - turns off in Android Studio, yet ScrCpy can't turn it off.

How odd...

@zoukyle
Copy link

zoukyle commented Jul 10, 2024

All the Honor phones with the recent updates are affected by this bug. This is very important to have a fix. Thanks!

@AndroidDeveloperLB
Copy link
Author

@eiyooooo @zoukyle For now, for the Honor device, I use the stable version of Android Studio.
Canary doesn't work with this feature at all over the past few versions.

They also claim that it will be fixed on the next version of Android itself, not sure about the IDE itself:
https://issuetracker.google.com/issues/348699128#comment27

@eiyooooo
Copy link
Contributor

For now, for the Honor device, I use the stable version of Android Studio.

The reason why stable version of Android Studio can work is because Honor devices still can use sdk ≤ 33 way to turn off screen.

https://github.com/JetBrains/android/blob/dbd9aeae0dc5b8c56ce2c7d51208ba26ea0f169b/streaming/screen-sharing-agent/app/src/main/cpp/session_environment.cc#L55-L66

Canary doesn't work with this feature at all over the past few versions.

Because

https://github.com/JetBrains/android/blob/63191c42e49429091566d1d4b4d5bddd82512a01/streaming/screen-sharing-agent/app/src/main/cpp/session_environment.cc#L56-L71

check Android version now.

@AndroidDeveloperLB
Copy link
Author

@eiyooooo So that's too bad.
Maybe ScrCpy can do something similar to the stable version of the IDE, then?

@eiyooooo
Copy link
Contributor

Maybe ScrCpy can do something similar to the stable version of the IDE, then?

You could bypass Android 14 method for Honor devices by this

#4823 (comment)

and compile a scrcpy server for Honor devices

@AndroidDeveloperLB
Copy link
Author

@eiyooooo I'm not the developer of ScrCpy. I only reported about this issue.

@naughtz
Copy link

naughtz commented Jul 16, 2024

scrcpy-server.zip
I compiled a scrcpy server for Honor devices, and it works on my honor device.
You just need to extract the zip file and replace your scrcpy-server. If you use macos, scrcpy-server is in /usr/local/share/scrcpy. @AndroidDeveloperLB

@AndroidDeveloperLB
Copy link
Author

AndroidDeveloperLB commented Jul 16, 2024

@naughtz Is it only for Honor, or should work for all?
@rom1v Do you know about this?

Anyway, confirmed to work with the Honor Magic 4 pro, Android 14.

@naughtz
Copy link

naughtz commented Jul 18, 2024

@naughtz Is it only for Honor, or should work for all? @rom1v Do you know about this?

Anyway, confirmed to work with the Honor Magic 4 pro, Android 14.

it's only for honor. it works on my magic vs2

@AndroidDeveloperLB
Copy link
Author

I hope for a new official version, then
:)

@AndroidDeveloperLB
Copy link
Author

@naughtz I've closed this issue as now it works fine.
Android Studio team also finally fixed it, and I wonder if they did something like here. If not, please check it out:
https://issuetracker.google.com/issues/353294033

@eiyooooo
Copy link
Contributor

Android Studio team also finally fixed it, and I wonder if they did something like here. If not, please check it out: https://issuetracker.google.com/issues/353294033

Similar idea to avoid invoke loadLibrary0 method

@eiyooooo
Copy link
Contributor

Android Studio team also finally fixed it, and I wonder if they did something like here. If not, please check it out: https://issuetracker.google.com/issues/353294033

Similar idea to avoid invoke loadLibrary0 method

if (physicalDisplayIds == null) {
Ln.e("Could not get physical display ids");
return false;
}

@rom1v I think here should have a fallback to turn only one display off instead of doing nothing.

rom1v added a commit that referenced this issue Jul 30, 2024
Some honor devices with Android 14 behave like Android 13 here.

Refs #4943 <#4943>
@rom1v
Copy link
Collaborator

rom1v commented Jul 30, 2024

I think here should have a fallback to turn only one display off instead of doing nothing.

Do you have physicalDisplayIds == null here on any device?

In theory, in this branch (on Android 14), SurfaceControl.getBuiltInDisplay() will not exist anyway.

AFAIK, the problem here is that the Honor Magic 4 with Android 14 is still really an Android 13 in this piece of code, and loading the native library with Runtime.loadLibrary0() in DisplayControl makes it crash even before this test.

IIUC, Android Studio does not support turning screen off for devices with Android 14 (except the Honor which behaves like an Android 13), but only for Android <= 13 or Android >= 15.

However, maybe we should use the multiple display ids even on the honor. Here is an alternative fix: honoroff.5.

Please someone with an Honor device test turning the screen off with this binary (built from honoroff.5):

@AndroidDeveloperLB
Copy link
Author

AndroidDeveloperLB commented Jul 30, 2024

@rom1v About "Android Studio does not support turning screen off for devices with Android 14", they fixed it.
Works fine on my Pixel 6 with Android 14.

About the "scrcpy-win64-honoroff.5.zip" file, it mirrors the device, but doesn't turn off the display, by using this:

...\scrcpy-win64-v2.5-37-gc19c688e0\scrcpy-noconsole.vbs -S --always-on-top --stay-awake --no-audio --show-touches -K
@eiyooooo
Copy link
Contributor

I think here should have a fallback to turn only one display off instead of doing nothing.

Do you have physicalDisplayIds == null here on any device?

No, but it's better to have a fallback in case physicalDisplayIds is null.

IIUC, Android Studio does not support turning screen off for devices with Android 14 (except the Honor which behaves like an Android 13), but only for Android <= 13 or Android >= 15.

Android Studio now support turning screen off for devices with Android 14

However, maybe we should use the multiple display ids even on the honor.

The SurfaceControl.getPhysicalDisplayIds function seems to no longer exist in Android 14 framework.jar.

@eiyooooo
Copy link
Contributor

eiyooooo commented Jul 30, 2024

I think here should have a fallback to turn only one display off instead of doing nothing.

Do you have physicalDisplayIds == null here on any device?

No, but it's better to have a fallback in case physicalDisplayIds is null.

--- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java
+++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java
@@ -328,20 +328,25 @@ public final class Device {
             boolean useDisplayControl =
                     Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasGetPhysicalDisplayIdsMethod();

-            // Change the power mode for all physical displays
-            long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds();
-            if (physicalDisplayIds == null) {
-                Ln.e("Could not get physical display ids");
-                return false;
+            if (useDisplayControl && Build.BRAND.equalsIgnoreCase("honor")) {
+                // Workaround for Honor devices with Android 14:
+                //  - <https://github.com/Genymobile/scrcpy/issues/4823>
+                //  - <https://github.com/Genymobile/scrcpy/issues/4943>
+                useDisplayControl = false;
             }

-            boolean allOk = true;
-            for (long physicalDisplayId : physicalDisplayIds) {
-                IBinder binder = useDisplayControl ? DisplayControl.getPhysicalDisplayToken(
-                        physicalDisplayId) : SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
-                allOk &= SurfaceControl.setDisplayPowerMode(binder, mode);
+            // Change the power mode for all physical displays
+            long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds();
+            if (physicalDisplayIds != null) {
+                boolean allOk = true;
+                for (long physicalDisplayId : physicalDisplayIds) {
+                    IBinder binder = useDisplayControl ? DisplayControl.getPhysicalDisplayToken(
+                            physicalDisplayId) : SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
+                    allOk &= SurfaceControl.setDisplayPowerMode(binder, mode);
+                }
+                return allOk;
             }
-            return allOk;
+            Ln.d("Could not get physical display ids, using built-in display");
         }

         // Older Android versions, only 1 display
image
@rom1v
Copy link
Collaborator

rom1v commented Jul 30, 2024

About "Android Studio does not support turning screen off for devices with Android 14", they fixed it.
Works fine on my Pixel 6 with Android 14.

Oh cool.

No, but it's better to have a fallback in case physicalDisplayIds is null.

In the code from Android studio, they check if the ids list is empty because they return an empty vector is the method is not found. We could add a fallback if SurfaceControl.getPhysicalDisplayIds() returns null, but I think it's secondary, and not directly related to this issue.

I'm trying to understand how this code (ignoring the part for API >= 35 for now) works for all devices on Android 14, in particular, both a Pixel device (as expected), but also an Honor, without special cases (and I don't have any Honor device to test).

In this bug report (#4943), it seems that the Honor device fails (and crashes) as soon as we load the native library (i.e. as soon as we call DisplayControl, since the native library is loaded from a static block).

According to the framework.jar of the Honor with Android 14:

  • SurfaceControl.getPhysicalDisplayToken() exists
  • SurfaceControl.getPhysicalDisplayIds() DOES NOT exist

Here, they load the native libraries using exactly the same mechanism as scrcpy, but only if BOTH getPhysicalDisplayToken() and getPhysicalDisplayIds() do not exist. So on the Honor, where one of them exists, the native library loading is not performed (preventing the crash).

And in DisplayControl::GetPhysicalDisplayIds(), they only checks if SurfaceControl.getPhysicalDisplayIds() exists, so they end up calling SurfaceControl.getInternalDisplayToken(). That's why it works, but it seems the Honor weirdness is detected as a side effect (based on the existence of SurfaceControl.getPhysicalDisplayToken(), which is not removed on Honor with Android 14, but removed from AOSP).

If I'm correct (I may be wrong), I think I prefer an explicit check for the Honor device (i.e. keeping the current code on dev with the explicit check applyToMultiPhysicalDisplays).

It remains to implement DisplayManager.requestDisplayPower() for Android 15.

I have no device with Android 15 for now, and the code is not released in AOSP, but we can implement it based on the code from Android Studio: https://android.googlesource.com/platform/tools/adt/idea/+/170a4f9317a8833f72661e2b6d51f64c685ce358/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.cc#46

diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Device.java b/server/src/main/java/com/genymobile/scrcpy/device/Device.java
index 5a1083fde..49bbe4796 100644
--- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java
+++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java
@@ -323,6 +323,11 @@ public final class Device {
      * @param mode one of the {@code POWER_MODE_*} constants
      */
     public static boolean setScreenPowerMode(int mode) {
+        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            // TODO Turn off secondary physical displays
+            return ServiceManager.getDisplayManager().requestDisplayPower(0, mode);
+        }
+
         boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
 
         if (applyToMultiPhysicalDisplays
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java
index dd92330cf..fd2c58d24 100644
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java
+++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java
@@ -19,6 +19,7 @@ import java.util.regex.Pattern;
 public final class DisplayManager {
     private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
     private Method createVirtualDisplayMethod;
+    private Method requestDisplayPowerMethod;
 
     static DisplayManager create() {
         try {
@@ -124,4 +125,20 @@ public final class DisplayManager {
         Method method = getCreateVirtualDisplayMethod();
         return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface);
     }
+
+    private Method getRequestDisplayPowerMethod() throws NoSuchMethodException {
+        if (requestDisplayPowerMethod == null) {
+            requestDisplayPowerMethod = manager.getClass().getMethod("requestDisplayPower", int.class, int.class);
+        }
+        return requestDisplayPowerMethod;
+    }
+
+    public boolean requestDisplayPower(int displayId, int state) {
+        try {
+            Method method = getRequestDisplayPowerMethod();
+            return (boolean) method.invoke(manager, displayId, state);
+        } catch (ReflectiveOperationException e) {
+            throw new AssertionError(e);
+        }
+    }
 }

Could someone with an Android 15 test this patch?

@eiyooooo
Copy link
Contributor

I have no device with Android 15 for now, and the code is not released in AOSP, but we can implement it based on the code from Android Studio: https://android.googlesource.com/platform/tools/adt/idea/+/170a4f9317a8833f72661e2b6d51f64c685ce358/streaming/screen-sharing-agent/app/src/main/cpp/accessors/display_manager.cc#46

image
framework-Pixel-8-Pro-Android-15.zip

@AndroidDeveloperLB
Copy link
Author

For Android 15, you can try the emulator, but it's probably a bad test....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants