1 /*
2  * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package java.awt;
27 
28 import java.awt.event.InputEvent;
29 import java.awt.event.KeyEvent;
30 import java.awt.geom.AffineTransform;
31 import java.awt.image.BaseMultiResolutionImage;
32 import java.awt.image.BufferedImage;
33 import java.awt.image.DataBufferInt;
34 import java.awt.image.DirectColorModel;
35 import java.awt.image.MultiResolutionImage;
36 import java.awt.image.Raster;
37 import java.awt.image.WritableRaster;
38 import java.awt.peer.RobotPeer;
39 
40 import sun.awt.AWTPermissions;
41 import sun.awt.ComponentFactory;
42 import sun.awt.SunToolkit;
43 import sun.awt.image.SunWritableRaster;
44 import sun.java2d.SunGraphicsEnvironment;
45 
46 /**
47  * This class is used to generate native system input events
48  * for the purposes of test automation, self-running demos, and
49  * other applications where control of the mouse and keyboard
50  * is needed. The primary purpose of Robot is to facilitate
51  * automated testing of Java platform implementations.
52  * <p>
53  * Using the class to generate input events differs from posting
54  * events to the AWT event queue or AWT components in that the
55  * events are generated in the platform's native input
56  * queue. For example, {@code Robot.mouseMove} will actually move
57  * the mouse cursor instead of just generating mouse move events.
58  * <p>
59  * Note that some platforms require special privileges or extensions
60  * to access low-level input control. If the current platform configuration
61  * does not allow input control, an {@code AWTException} will be thrown
62  * when trying to construct Robot objects. For example, X-Window systems
63  * will throw the exception if the XTEST 2.2 standard extension is not supported
64  * (or not enabled) by the X server.
65  * <p>
66  * Applications that use Robot for purposes other than self-testing should
67  * handle these error conditions gracefully.
68  *
69  * @author      Robi Khan
70  * @since       1.3
71  */
72 public class Robot {
73     private static final int MAX_DELAY = 60000;
74     private RobotPeer peer;
75     private boolean isAutoWaitForIdle = false;
76     private int autoDelay = 0;
77     private static int LEGAL_BUTTON_MASK = 0;
78 
79     private DirectColorModel screenCapCM = null;
80 
81     /**
82      * Constructs a Robot object in the coordinate system of the primary screen.
83      *
84      * @throws  AWTException if the platform configuration does not allow
85      * low-level input control.  This exception is always thrown when
86      * GraphicsEnvironment.isHeadless() returns true
87      * @throws  SecurityException if {@code createRobot} permission is not granted
88      * @see     java.awt.GraphicsEnvironment#isHeadless
89      * @see     SecurityManager#checkPermission
90      * @see     AWTPermission
91      */
Robot()92     public Robot() throws AWTException {
93         if (GraphicsEnvironment.isHeadless()) {
94             throw new AWTException("headless environment");
95         }
96         init(GraphicsEnvironment.getLocalGraphicsEnvironment()
97             .getDefaultScreenDevice());
98     }
99 
100     /**
101      * Creates a Robot for the given screen device. Coordinates passed
102      * to Robot method calls like mouseMove, getPixelColor and
103      * createScreenCapture will be interpreted as being in the same coordinate
104      * system as the specified screen. Note that depending on the platform
105      * configuration, multiple screens may either:
106      * <ul>
107      * <li>share the same coordinate system to form a combined virtual screen</li>
108      * <li>use different coordinate systems to act as independent screens</li>
109      * </ul>
110      * <p>
111      * If screen devices are reconfigured such that the coordinate system is
112      * affected, the behavior of existing Robot objects is undefined.
113      *
114      * @param screen    A screen GraphicsDevice indicating the coordinate
115      *                  system the Robot will operate in.
116      * @throws  AWTException if the platform configuration does not allow
117      * low-level input control.  This exception is always thrown when
118      * GraphicsEnvironment.isHeadless() returns true.
119      * @throws  IllegalArgumentException if {@code screen} is not a screen
120      *          GraphicsDevice.
121      * @throws  SecurityException if {@code createRobot} permission is not granted
122      * @see     java.awt.GraphicsEnvironment#isHeadless
123      * @see     GraphicsDevice
124      * @see     SecurityManager#checkPermission
125      * @see     AWTPermission
126      */
Robot(GraphicsDevice screen)127     public Robot(GraphicsDevice screen) throws AWTException {
128         checkIsScreenDevice(screen);
129         init(screen);
130     }
131 
init(GraphicsDevice screen)132     private void init(GraphicsDevice screen) throws AWTException {
133         checkRobotAllowed();
134         Toolkit toolkit = Toolkit.getDefaultToolkit();
135         if (toolkit instanceof ComponentFactory) {
136             peer = ((ComponentFactory)toolkit).createRobot(this, screen);
137         }
138         initLegalButtonMask();
139     }
140 
141     @SuppressWarnings("deprecation")
initLegalButtonMask()142     private static synchronized void initLegalButtonMask() {
143         if (LEGAL_BUTTON_MASK != 0) return;
144 
145         int tmpMask = 0;
146         if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){
147             if (Toolkit.getDefaultToolkit() instanceof SunToolkit) {
148                 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons();
149                 for (int i = 0; i < buttonsNumber; i++){
150                     tmpMask |= InputEvent.getMaskForButton(i+1);
151                 }
152             }
153         }
154         tmpMask |= InputEvent.BUTTON1_MASK|
155             InputEvent.BUTTON2_MASK|
156             InputEvent.BUTTON3_MASK|
157             InputEvent.BUTTON1_DOWN_MASK|
158             InputEvent.BUTTON2_DOWN_MASK|
159             InputEvent.BUTTON3_DOWN_MASK;
160         LEGAL_BUTTON_MASK = tmpMask;
161     }
162 
163     /* determine if the security policy allows Robot's to be created */
checkRobotAllowed()164     private void checkRobotAllowed() {
165         SecurityManager security = System.getSecurityManager();
166         if (security != null) {
167             security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION);
168         }
169     }
170 
171     /* check if the given device is a screen device */
checkIsScreenDevice(GraphicsDevice device)172     private void checkIsScreenDevice(GraphicsDevice device) {
173         if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
174             throw new IllegalArgumentException("not a valid screen device");
175         }
176     }
177 
178     /**
179      * Moves mouse pointer to given screen coordinates.
180      * @param x         X position
181      * @param y         Y position
182      */
mouseMove(int x, int y)183     public synchronized void mouseMove(int x, int y) {
184         peer.mouseMove(x, y);
185         afterEvent();
186     }
187 
188     /**
189      * Presses one or more mouse buttons.  The mouse buttons should
190      * be released using the {@link #mouseRelease(int)} method.
191      *
192      * @param buttons the Button mask; a combination of one or more
193      * mouse button masks.
194      * <p>
195      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
196      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
197      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
198      * and values returned by the
199      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
200      *
201      * The valid combination also depends on a
202      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
203      * <ul>
204      * <li> If support for extended mouse buttons is
205      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
206      * then it is allowed to use only the following standard button masks:
207      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
208      * {@code InputEvent.BUTTON3_DOWN_MASK}.
209      * <li> If support for extended mouse buttons is
210      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
211      * then it is allowed to use the standard button masks
212      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
213      * In that way, it is allowed to use the button masks corresponding to the buttons
214      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
215      * <br>
216      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
217      * method to obtain the mask for any mouse button by its number.
218      * </ul>
219      * <p>
220      * The following standard button masks are also accepted:
221      * <ul>
222      * <li>{@code InputEvent.BUTTON1_MASK}
223      * <li>{@code InputEvent.BUTTON2_MASK}
224      * <li>{@code InputEvent.BUTTON3_MASK}
225      * </ul>
226      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
227      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
228      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
229      * should be used, but both those models should not be mixed.
230      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
231      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
232      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
233      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
234      * @see #mouseRelease(int)
235      * @see InputEvent#getMaskForButton(int)
236      * @see Toolkit#areExtraMouseButtonsEnabled()
237      * @see java.awt.MouseInfo#getNumberOfButtons()
238      * @see java.awt.event.MouseEvent
239      */
mousePress(int buttons)240     public synchronized void mousePress(int buttons) {
241         checkButtonsArgument(buttons);
242         peer.mousePress(buttons);
243         afterEvent();
244     }
245 
246     /**
247      * Releases one or more mouse buttons.
248      *
249      * @param buttons the Button mask; a combination of one or more
250      * mouse button masks.
251      * <p>
252      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
253      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
254      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
255      * and values returned by the
256      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
257      *
258      * The valid combination also depends on a
259      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
260      * <ul>
261      * <li> If the support for extended mouse buttons is
262      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
263      * then it is allowed to use only the following standard button masks:
264      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
265      * {@code InputEvent.BUTTON3_DOWN_MASK}.
266      * <li> If the support for extended mouse buttons is
267      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
268      * then it is allowed to use the standard button masks
269      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
270      * In that way, it is allowed to use the button masks corresponding to the buttons
271      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
272      * <br>
273      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
274      * method to obtain the mask for any mouse button by its number.
275      * </ul>
276      * <p>
277      * The following standard button masks are also accepted:
278      * <ul>
279      * <li>{@code InputEvent.BUTTON1_MASK}
280      * <li>{@code InputEvent.BUTTON2_MASK}
281      * <li>{@code InputEvent.BUTTON3_MASK}
282      * </ul>
283      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
284      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
285      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
286      * should be used, but both those models should not be mixed.
287      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
288      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
289      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
290      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
291      * @see #mousePress(int)
292      * @see InputEvent#getMaskForButton(int)
293      * @see Toolkit#areExtraMouseButtonsEnabled()
294      * @see java.awt.MouseInfo#getNumberOfButtons()
295      * @see java.awt.event.MouseEvent
296      */
mouseRelease(int buttons)297     public synchronized void mouseRelease(int buttons) {
298         checkButtonsArgument(buttons);
299         peer.mouseRelease(buttons);
300         afterEvent();
301     }
302 
checkButtonsArgument(int buttons)303     private void checkButtonsArgument(int buttons) {
304         if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) {
305             throw new IllegalArgumentException("Invalid combination of button flags");
306         }
307     }
308 
309     /**
310      * Rotates the scroll wheel on wheel-equipped mice.
311      *
312      * @param wheelAmt  number of "notches" to move the mouse wheel
313      *                  Negative values indicate movement up/away from the user,
314      *                  positive values indicate movement down/towards the user.
315      *
316      * @since 1.4
317      */
mouseWheel(int wheelAmt)318     public synchronized void mouseWheel(int wheelAmt) {
319         peer.mouseWheel(wheelAmt);
320         afterEvent();
321     }
322 
323     /**
324      * Presses a given key.  The key should be released using the
325      * {@code keyRelease} method.
326      * <p>
327      * Key codes that have more than one physical key associated with them
328      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
329      * left or right shift key) will map to the left key.
330      *
331      * @param   keycode Key to press (e.g. {@code KeyEvent.VK_A})
332      * @throws  IllegalArgumentException if {@code keycode} is not
333      *          a valid key
334      * @see     #keyRelease(int)
335      * @see     java.awt.event.KeyEvent
336      */
keyPress(int keycode)337     public synchronized void keyPress(int keycode) {
338         checkKeycodeArgument(keycode);
339         peer.keyPress(keycode);
340         afterEvent();
341     }
342 
343     /**
344      * Releases a given key.
345      * <p>
346      * Key codes that have more than one physical key associated with them
347      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
348      * left or right shift key) will map to the left key.
349      *
350      * @param   keycode Key to release (e.g. {@code KeyEvent.VK_A})
351      * @throws  IllegalArgumentException if {@code keycode} is not a
352      *          valid key
353      * @see  #keyPress(int)
354      * @see     java.awt.event.KeyEvent
355      */
keyRelease(int keycode)356     public synchronized void keyRelease(int keycode) {
357         checkKeycodeArgument(keycode);
358         peer.keyRelease(keycode);
359         afterEvent();
360     }
361 
checkKeycodeArgument(int keycode)362     private void checkKeycodeArgument(int keycode) {
363         // rather than build a big table or switch statement here, we'll
364         // just check that the key isn't VK_UNDEFINED and assume that the
365         // peer implementations will throw an exception for other bogus
366         // values e.g. -1, 999999
367         if (keycode == KeyEvent.VK_UNDEFINED) {
368             throw new IllegalArgumentException("Invalid key code");
369         }
370     }
371 
372     /**
373      * Returns the color of a pixel at the given screen coordinates.
374      * @param   x       X position of pixel
375      * @param   y       Y position of pixel
376      * @return  Color of the pixel
377      */
getPixelColor(int x, int y)378     public synchronized Color getPixelColor(int x, int y) {
379         checkScreenCaptureAllowed();
380         AffineTransform tx = GraphicsEnvironment.
381                 getLocalGraphicsEnvironment().getDefaultScreenDevice().
382                 getDefaultConfiguration().getDefaultTransform();
383         x = (int) (x * tx.getScaleX());
384         y = (int) (y * tx.getScaleY());
385         Color color = new Color(peer.getRGBPixel(x, y));
386         return color;
387     }
388 
389     /**
390      * Creates an image containing pixels read from the screen.  This image does
391      * not include the mouse cursor.
392      * @param   screenRect      Rect to capture in screen coordinates
393      * @return  The captured image
394      * @throws  IllegalArgumentException if {@code screenRect} width and height are not greater than zero
395      * @throws  SecurityException if {@code readDisplayPixels} permission is not granted
396      * @see     SecurityManager#checkPermission
397      * @see     AWTPermission
398      */
createScreenCapture(Rectangle screenRect)399     public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
400         return createCompatibleImage(screenRect, false)[0];
401     }
402 
403     /**
404      * Creates an image containing pixels read from the screen.
405      * This image does not include the mouse cursor.
406      * This method can be used in case there is a scaling transform
407      * from user space to screen (device) space.
408      * Typically this means that the display is a high resolution screen,
409      * although strictly it means any case in which there is such a transform.
410      * Returns a {@link java.awt.image.MultiResolutionImage}.
411      * <p>
412      * For a non-scaled display, the {@code MultiResolutionImage}
413      * will have one image variant:
414      * <ul>
415      * <li> Base Image with user specified size.
416      * </ul>
417      * <p>
418      * For a high resolution display where there is a scaling transform,
419      * the {@code MultiResolutionImage} will have two image variants:
420      * <ul>
421      * <li> Base Image with user specified size. This is scaled from the screen.
422      * <li> Native device resolution image with device size pixels.
423      * </ul>
424      * <p>
425      * Example:
426      * <pre>{@code
427      *      Image nativeResImage;
428      *      MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(frame.getBounds());
429      *      List<Image> resolutionVariants = mrImage.getResolutionVariants();
430      *      if (resolutionVariants.size() > 1) {
431      *          nativeResImage = resolutionVariants.get(1);
432      *      } else {
433      *          nativeResImage = resolutionVariants.get(0);
434      *      }
435      * }</pre>
436      * @param   screenRect     Rect to capture in screen coordinates
437      * @return  The captured image
438      * @throws  IllegalArgumentException if {@code screenRect} width and height are not greater than zero
439      * @throws  SecurityException if {@code readDisplayPixels} permission is not granted
440      * @see     SecurityManager#checkPermission
441      * @see     AWTPermission
442      *
443      * @since 9
444      */
445     public synchronized MultiResolutionImage
createMultiResolutionScreenCapture(Rectangle screenRect)446             createMultiResolutionScreenCapture(Rectangle screenRect) {
447 
448         return new BaseMultiResolutionImage(
449                 createCompatibleImage(screenRect, true));
450     }
451 
452     private synchronized BufferedImage[]
createCompatibleImage(Rectangle screenRect, boolean isHiDPI)453             createCompatibleImage(Rectangle screenRect, boolean isHiDPI) {
454 
455         checkScreenCaptureAllowed();
456 
457         checkValidRect(screenRect);
458 
459         BufferedImage lowResolutionImage;
460         BufferedImage highResolutionImage;
461         DataBufferInt buffer;
462         WritableRaster raster;
463         BufferedImage[] imageArray;
464 
465         if (screenCapCM == null) {
466             /*
467              * Fix for 4285201
468              * Create a DirectColorModel equivalent to the default RGB ColorModel,
469              * except with no Alpha component.
470              */
471 
472             screenCapCM = new DirectColorModel(24,
473                     /* red mask */ 0x00FF0000,
474                     /* green mask */ 0x0000FF00,
475                     /* blue mask */ 0x000000FF);
476         }
477 
478         int[] bandmasks = new int[3];
479         bandmasks[0] = screenCapCM.getRedMask();
480         bandmasks[1] = screenCapCM.getGreenMask();
481         bandmasks[2] = screenCapCM.getBlueMask();
482 
483         // need to sync the toolkit prior to grabbing the pixels since in some
484         // cases rendering to the screen may be delayed
485         Toolkit.getDefaultToolkit().sync();
486 
487         GraphicsConfiguration gc = GraphicsEnvironment
488                 .getLocalGraphicsEnvironment()
489                 .getDefaultScreenDevice().
490                 getDefaultConfiguration();
491         gc = SunGraphicsEnvironment.getGraphicsConfigurationAtPoint(
492                 gc, screenRect.getCenterX(), screenRect.getCenterY());
493 
494         AffineTransform tx = gc.getDefaultTransform();
495         double uiScaleX = tx.getScaleX();
496         double uiScaleY = tx.getScaleY();
497         int[] pixels;
498 
499         if (uiScaleX == 1 && uiScaleY == 1) {
500 
501             pixels = peer.getRGBPixels(screenRect);
502             buffer = new DataBufferInt(pixels, pixels.length);
503 
504             bandmasks[0] = screenCapCM.getRedMask();
505             bandmasks[1] = screenCapCM.getGreenMask();
506             bandmasks[2] = screenCapCM.getBlueMask();
507 
508             raster = Raster.createPackedRaster(buffer, screenRect.width,
509                     screenRect.height, screenRect.width, bandmasks, null);
510             SunWritableRaster.makeTrackable(buffer);
511 
512             highResolutionImage = new BufferedImage(screenCapCM, raster,
513                     false, null);
514             imageArray = new BufferedImage[1];
515             imageArray[0] = highResolutionImage;
516 
517         } else {
518 
519             int sX = (int) Math.floor(screenRect.x * uiScaleX);
520             int sY = (int) Math.floor(screenRect.y * uiScaleY);
521             int sWidth = (int) Math.ceil(screenRect.width * uiScaleX);
522             int sHeight = (int) Math.ceil(screenRect.height * uiScaleY);
523             int[] temppixels;
524             Rectangle scaledRect = new Rectangle(sX, sY, sWidth, sHeight);
525             temppixels = peer.getRGBPixels(scaledRect);
526 
527             // HighResolutionImage
528             pixels = temppixels;
529             buffer = new DataBufferInt(pixels, pixels.length);
530             raster = Raster.createPackedRaster(buffer, scaledRect.width,
531                     scaledRect.height, scaledRect.width, bandmasks, null);
532             SunWritableRaster.makeTrackable(buffer);
533 
534             highResolutionImage = new BufferedImage(screenCapCM, raster,
535                     false, null);
536 
537 
538             // LowResolutionImage
539             lowResolutionImage = new BufferedImage(screenRect.width,
540                     screenRect.height, highResolutionImage.getType());
541             Graphics2D g = lowResolutionImage.createGraphics();
542             g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
543                     RenderingHints.VALUE_INTERPOLATION_BILINEAR);
544             g.setRenderingHint(RenderingHints.KEY_RENDERING,
545                     RenderingHints.VALUE_RENDER_QUALITY);
546             g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
547                     RenderingHints.VALUE_ANTIALIAS_ON);
548             g.drawImage(highResolutionImage, 0, 0,
549                     screenRect.width, screenRect.height,
550                     0, 0, scaledRect.width, scaledRect.height, null);
551             g.dispose();
552 
553             if(!isHiDPI) {
554                 imageArray = new BufferedImage[1];
555                 imageArray[0] = lowResolutionImage;
556             } else {
557                 imageArray = new BufferedImage[2];
558                 imageArray[0] = lowResolutionImage;
559                 imageArray[1] = highResolutionImage;
560             }
561 
562         }
563 
564         return imageArray;
565     }
566 
checkValidRect(Rectangle rect)567     private static void checkValidRect(Rectangle rect) {
568         if (rect.width <= 0 || rect.height <= 0) {
569             throw new IllegalArgumentException("Rectangle width and height must be > 0");
570         }
571     }
572 
checkScreenCaptureAllowed()573     private static void checkScreenCaptureAllowed() {
574         SecurityManager security = System.getSecurityManager();
575         if (security != null) {
576             security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION);
577         }
578     }
579 
580     /*
581      * Called after an event is generated
582      */
afterEvent()583     private void afterEvent() {
584         autoWaitForIdle();
585         autoDelay();
586     }
587 
588     /**
589      * Returns whether this Robot automatically invokes {@code waitForIdle}
590      * after generating an event.
591      * @return Whether {@code waitForIdle} is automatically called
592      */
isAutoWaitForIdle()593     public synchronized boolean isAutoWaitForIdle() {
594         return isAutoWaitForIdle;
595     }
596 
597     /**
598      * Sets whether this Robot automatically invokes {@code waitForIdle}
599      * after generating an event.
600      * @param   isOn    Whether {@code waitForIdle} is automatically invoked
601      */
setAutoWaitForIdle(boolean isOn)602     public synchronized void setAutoWaitForIdle(boolean isOn) {
603         isAutoWaitForIdle = isOn;
604     }
605 
606     /*
607      * Calls waitForIdle after every event if so desired.
608      */
autoWaitForIdle()609     private void autoWaitForIdle() {
610         if (isAutoWaitForIdle) {
611             waitForIdle();
612         }
613     }
614 
615     /**
616      * Returns the number of milliseconds this Robot sleeps after generating an event.
617      *
618      * @return the delay duration in milliseconds
619      */
getAutoDelay()620     public synchronized int getAutoDelay() {
621         return autoDelay;
622     }
623 
624     /**
625      * Sets the number of milliseconds this Robot sleeps after generating an event.
626      *
627      * @param  ms the delay duration in milliseconds
628      * @throws IllegalArgumentException If {@code ms}
629      *         is not between 0 and 60,000 milliseconds inclusive
630      */
setAutoDelay(int ms)631     public synchronized void setAutoDelay(int ms) {
632         checkDelayArgument(ms);
633         autoDelay = ms;
634     }
635 
636     /*
637      * Automatically sleeps for the specified interval after event generated.
638      */
autoDelay()639     private void autoDelay() {
640         delay(autoDelay);
641     }
642 
643     /**
644      * Sleeps for the specified time.
645      * To catch any {@code InterruptedException}s that occur,
646      * {@code Thread.sleep()} may be used instead.
647      *
648      * @param  ms time to sleep in milliseconds
649      * @throws IllegalArgumentException if {@code ms}
650      *         is not between 0 and 60,000 milliseconds inclusive
651      * @see java.lang.Thread#sleep
652      */
delay(int ms)653     public synchronized void delay(int ms) {
654         checkDelayArgument(ms);
655         try {
656             Thread.sleep(ms);
657         } catch(InterruptedException ite) {
658             ite.printStackTrace();
659         }
660     }
661 
checkDelayArgument(int ms)662     private void checkDelayArgument(int ms) {
663         if (ms < 0 || ms > MAX_DELAY) {
664             throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
665         }
666     }
667 
668     /**
669      * Waits until all events currently on the event queue have been processed.
670      * @throws  IllegalThreadStateException if called on the AWT event dispatching thread
671      */
waitForIdle()672     public synchronized void waitForIdle() {
673         checkNotDispatchThread();
674         SunToolkit.flushPendingEvents();
675         ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
676     }
677 
checkNotDispatchThread()678     private void checkNotDispatchThread() {
679         if (EventQueue.isDispatchThread()) {
680             throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
681         }
682     }
683 
684     /**
685      * Returns a string representation of this Robot.
686      *
687      * @return  the string representation.
688      */
689     @Override
toString()690     public synchronized String toString() {
691         String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
692         return getClass().getName() + "[ " + params + " ]";
693     }
694 }
695