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