1 package com.genymobile.scrcpy;
2 
3 import com.genymobile.scrcpy.wrappers.ServiceManager;
4 import com.genymobile.scrcpy.wrappers.SurfaceControl;
5 
6 import android.graphics.Rect;
7 import android.os.Build;
8 import android.os.IBinder;
9 import android.os.RemoteException;
10 import android.view.IRotationWatcher;
11 import android.view.InputEvent;
12 
13 public final class Device {
14 
15     public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
16     public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
17 
18     public interface RotationListener {
onRotationChanged(int rotation)19         void onRotationChanged(int rotation);
20     }
21 
22     private final ServiceManager serviceManager = new ServiceManager();
23 
24     private ScreenInfo screenInfo;
25     private RotationListener rotationListener;
26 
Device(Options options)27     public Device(Options options) {
28         screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
29         registerRotationWatcher(new IRotationWatcher.Stub() {
30             @Override
31             public void onRotationChanged(int rotation) throws RemoteException {
32                 synchronized (Device.this) {
33                     screenInfo = screenInfo.withRotation(rotation);
34 
35                     // notify
36                     if (rotationListener != null) {
37                         rotationListener.onRotationChanged(rotation);
38                     }
39                 }
40             }
41         });
42     }
43 
getScreenInfo()44     public synchronized ScreenInfo getScreenInfo() {
45         return screenInfo;
46     }
47 
computeScreenInfo(Rect crop, int maxSize)48     private ScreenInfo computeScreenInfo(Rect crop, int maxSize) {
49         DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
50         boolean rotated = (displayInfo.getRotation() & 1) != 0;
51         Size deviceSize = displayInfo.getSize();
52         Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
53         if (crop != null) {
54             if (rotated) {
55                 // the crop (provided by the user) is expressed in the natural orientation
56                 crop = flipRect(crop);
57             }
58             if (!contentRect.intersect(crop)) {
59                 // intersect() changes contentRect so that it is intersected with crop
60                 Ln.w("Crop rectangle (" + formatCrop(crop) + ") does not intersect device screen (" + formatCrop(deviceSize.toRect()) + ")");
61                 contentRect = new Rect(); // empty
62             }
63         }
64 
65         Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
66         return new ScreenInfo(contentRect, videoSize, rotated);
67     }
68 
formatCrop(Rect rect)69     private static String formatCrop(Rect rect) {
70         return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top;
71     }
72 
73     @SuppressWarnings("checkstyle:MagicNumber")
computeVideoSize(int w, int h, int maxSize)74     private static Size computeVideoSize(int w, int h, int maxSize) {
75         // Compute the video size and the padding of the content inside this video.
76         // Principle:
77         // - scale down the great side of the screen to maxSize (if necessary);
78         // - scale down the other side so that the aspect ratio is preserved;
79         // - round this value to the nearest multiple of 8 (H.264 only accepts multiples of 8)
80         w &= ~7; // in case it's not a multiple of 8
81         h &= ~7;
82         if (maxSize > 0) {
83             if (BuildConfig.DEBUG && maxSize % 8 != 0) {
84                 throw new AssertionError("Max size must be a multiple of 8");
85             }
86             boolean portrait = h > w;
87             int major = portrait ? h : w;
88             int minor = portrait ? w : h;
89             if (major > maxSize) {
90                 int minorExact = minor * maxSize / major;
91                 // +4 to round the value to the nearest multiple of 8
92                 minor = (minorExact + 4) & ~7;
93                 major = maxSize;
94             }
95             w = portrait ? minor : major;
96             h = portrait ? major : minor;
97         }
98         return new Size(w, h);
99     }
100 
getPhysicalPoint(Position position)101     public Point getPhysicalPoint(Position position) {
102         // it hides the field on purpose, to read it with a lock
103         @SuppressWarnings("checkstyle:HiddenField")
104         ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
105         Size videoSize = screenInfo.getVideoSize();
106         Size clientVideoSize = position.getScreenSize();
107         if (!videoSize.equals(clientVideoSize)) {
108             // The client sends a click relative to a video with wrong dimensions,
109             // the device may have been rotated since the event was generated, so ignore the event
110             return null;
111         }
112         Rect contentRect = screenInfo.getContentRect();
113         Point point = position.getPoint();
114         int scaledX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth();
115         int scaledY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight();
116         return new Point(scaledX, scaledY);
117     }
118 
getDeviceName()119     public static String getDeviceName() {
120         return Build.MODEL;
121     }
122 
injectInputEvent(InputEvent inputEvent, int mode)123     public boolean injectInputEvent(InputEvent inputEvent, int mode) {
124         return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
125     }
126 
isScreenOn()127     public boolean isScreenOn() {
128         return serviceManager.getPowerManager().isScreenOn();
129     }
130 
registerRotationWatcher(IRotationWatcher rotationWatcher)131     public void registerRotationWatcher(IRotationWatcher rotationWatcher) {
132         serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher);
133     }
134 
setRotationListener(RotationListener rotationListener)135     public synchronized void setRotationListener(RotationListener rotationListener) {
136         this.rotationListener = rotationListener;
137     }
138 
expandNotificationPanel()139     public void expandNotificationPanel() {
140         serviceManager.getStatusBarManager().expandNotificationsPanel();
141     }
142 
collapsePanels()143     public void collapsePanels() {
144         serviceManager.getStatusBarManager().collapsePanels();
145     }
146 
getClipboardText()147     public String getClipboardText() {
148         CharSequence s = serviceManager.getClipboardManager().getText();
149         if (s == null) {
150             return null;
151         }
152         return s.toString();
153     }
154 
setClipboardText(String text)155     public void setClipboardText(String text) {
156         serviceManager.getClipboardManager().setText(text);
157         Ln.i("Device clipboard set");
158     }
159 
160     /**
161      * @param mode one of the {@code SCREEN_POWER_MODE_*} constants
162      */
setScreenPowerMode(int mode)163     public void setScreenPowerMode(int mode) {
164         IBinder d = SurfaceControl.getBuiltInDisplay(0);
165         SurfaceControl.setDisplayPowerMode(d, mode);
166         Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
167     }
168 
flipRect(Rect crop)169     static Rect flipRect(Rect crop) {
170         return new Rect(crop.top, crop.left, crop.bottom, crop.right);
171     }
172 }
173