1 /*
   2  * Copyright (c) 2005, 2014 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  @test
  26  @bug 6275887 6429971 6459792
  27  @summary Test that we don't crash when alt+tabbing in and out of
  28          fullscreen app
  29  @author Dmitri.Trembovetski@sun.com: area=FullScreen
  30  @run main/othervm/timeout=100  AltTabCrashTest -auto -changedm
  31  @run main/othervm/timeout=100 -Dsun.java2d.d3d=True AltTabCrashTest -auto -changedm
  32  @run main/othervm/timeout=100 -Dsun.java2d.d3d=True AltTabCrashTest -auto -usebs -changedm
  33  @run main/othervm/timeout=100 -Dsun.java2d.opengl=True AltTabCrashTest -auto
  34  @key randomness
  35 */
  36 
  37 import java.awt.AWTException;
  38 import java.awt.Color;
  39 import java.awt.DisplayMode;
  40 import java.awt.Frame;
  41 import java.awt.Graphics;
  42 import java.awt.Graphics2D;
  43 import java.awt.GraphicsDevice;
  44 import java.awt.GraphicsEnvironment;
  45 import java.awt.Image;
  46 import java.awt.RenderingHints;
  47 import java.awt.Robot;
  48 import java.awt.event.KeyAdapter;
  49 import java.awt.event.KeyEvent;
  50 import java.awt.event.MouseAdapter;
  51 import java.awt.event.MouseEvent;
  52 import java.awt.image.BufferStrategy;
  53 import java.awt.image.BufferedImage;
  54 import java.awt.image.VolatileImage;
  55 import java.util.Random;
  56 import java.util.Vector;
  57 
  58 /**
  59  * Note that the alt+tabbing in and out part will most likely only work
  60  * on Windows, and only if there are no interventions.
  61  */
  62 
  63 public class AltTabCrashTest extends Frame {
  64     public static int width;
  65     public static int height;
  66     public static volatile boolean autoMode;
  67     public static boolean useBS;
  68     public static final int NUM_OF_BALLS = 70;
  69     // number of times to alt+tab in and out of the app
  70     public static int altTabs = 5;
  71     private final Vector<Ball> balls = new Vector<>();
  72     GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment()
  73         .getDefaultScreenDevice();
  74     VolatileImage vimg = null;
  75     BufferStrategy bufferStrategy = null;
  76     volatile boolean timeToQuit = false;
  77     static final Object lock = new Object();
  78 
  79     enum SpriteType {
  80         OVALS, VIMAGES, BIMAGES, AAOVALS, TEXT
  81     }
  82 
  83     private static boolean changeDM = false;
  84     private static SpriteType spriteType;
  85     static Random rnd = new Random();
  86 
  87     public AltTabCrashTest( ) {
  88         addKeyListener(new KeyAdapter() {
  89             public void keyPressed(KeyEvent e) {
  90                 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
  91                     timeToQuit = true;
  92                 }
  93             }
  94         });
  95         setIgnoreRepaint(true);
  96         addMouseListener(new MouseHandler());
  97         for (int i = 0; i < NUM_OF_BALLS; i++) {
  98             int x = 50 + rnd.nextInt(550), y = 50 + rnd.nextInt(400);
  99 
 100             balls.addElement(createRandomBall(y, x));
 101         }
 102         setUndecorated(true);
 103         gd.setFullScreenWindow(this);
 104         GraphicsDevice gd = getGraphicsConfiguration().getDevice();
 105         if (gd.isDisplayChangeSupported() && changeDM) {
 106             DisplayMode dm = findDisplayMode();
 107             if (dm != null) {
 108                 try {
 109                     gd.setDisplayMode(dm);
 110                 } catch (IllegalArgumentException iae) {
 111                     System.err.println("Error setting display mode");
 112                 }
 113             }
 114         }
 115         if (useBS) {
 116             createBufferStrategy(2);
 117             bufferStrategy = getBufferStrategy();
 118         } else {
 119             Graphics2D g = (Graphics2D) getGraphics();
 120             render(g);
 121             g.dispose();
 122         }
 123         Thread t = new BallThread();
 124         t.start();
 125         if (autoMode) {
 126             Thread tt = new AltTabberThread();
 127             tt.start();
 128             synchronized (lock) {
 129                 while (!timeToQuit) {
 130                     try {
 131                         lock.wait(200);
 132                     } catch (InterruptedException ex) {
 133                         ex.printStackTrace();
 134                     }
 135                 }
 136             }
 137             t = null;
 138             dispose();
 139         }
 140     }
 141 
 142     private Ball createRandomBall(final int y, final int x) {
 143         Ball b;
 144         SpriteType type;
 145 
 146         if (spriteType == null) {
 147             int index = rnd.nextInt(SpriteType.values().length);
 148             type = SpriteType.values()[index];
 149         } else {
 150             type = spriteType;
 151         }
 152         switch (type) {
 153             case VIMAGES: b = new VISpriteBall(x, y); break;
 154             case AAOVALS: b = new AAOvalBall(x, y); break;
 155             case BIMAGES: b = new BISpriteBall(x, y); break;
 156             case TEXT: b = new TextBall(x,y, "Text Sprite!"); break;
 157             default: b = new Ball(x, y); break;
 158         }
 159         return b;
 160     }
 161 
 162     private class MouseHandler extends MouseAdapter  {
 163         public void mousePressed(MouseEvent e) {
 164             synchronized (balls) {
 165                 balls.addElement(createRandomBall(e.getX(), e.getY()));
 166             }
 167         }
 168     }
 169 
 170     private class AltTabberThread extends Thread {
 171         Robot robot;
 172 
 173         void pressAltTab() {
 174             robot.keyPress(KeyEvent.VK_ALT);
 175             robot.keyPress(KeyEvent.VK_TAB);
 176             robot.keyRelease(KeyEvent.VK_TAB);
 177             robot.keyRelease(KeyEvent.VK_ALT);
 178         }
 179         void pressShiftAltTab() {
 180             robot.keyPress(KeyEvent.VK_SHIFT);
 181             pressAltTab();
 182             robot.keyRelease(KeyEvent.VK_SHIFT);
 183         }
 184         public void run() {
 185             try {
 186                 robot = new Robot();
 187                 robot.setAutoDelay(200);
 188             } catch (AWTException e) {
 189                 throw new RuntimeException("Can't create robot");
 190             }
 191             boolean out = true;
 192             while (altTabs-- > 0 && !timeToQuit) {
 193                 System.err.println("Alt+tabber Iteration: "+altTabs);
 194                 try { Thread.sleep(2500); } catch (InterruptedException ex) {}
 195 
 196                 if (out) {
 197                     System.err.println("Issuing alt+tab");
 198                     pressAltTab();
 199                 } else {
 200                     System.err.println("Issuing shift ");
 201                     pressShiftAltTab();
 202                 }
 203                 out = !out;
 204             }
 205             System.err.println("Alt+tabber finished.");
 206             synchronized (lock) {
 207                 timeToQuit = true;
 208                 lock.notify();
 209             }
 210         }
 211     }
 212 
 213     private class BallThread extends Thread {
 214         public void run() {
 215             while (!timeToQuit) {
 216                 if (useBS) {
 217                     renderToBS();
 218                     bufferStrategy.show();
 219                 } else {
 220                     Graphics g = AltTabCrashTest.this.getGraphics();
 221                     render(g);
 222                     g.dispose();
 223                 }
 224             }
 225             gd.setFullScreenWindow(null);
 226             AltTabCrashTest.this.dispose();
 227         }
 228     }
 229 
 230     static class Ball {
 231 
 232         int x, y;     // current location
 233         int dx, dy;   // motion delta
 234         int diameter = 40;
 235         Color color = Color.red;
 236 
 237         public Ball() {
 238         }
 239 
 240         public Ball(int x, int y) {
 241             this.x = x;
 242             this.y = y;
 243             dx = x % 20 + 1;
 244             dy = y % 20 + 1;
 245             color = new Color(rnd.nextInt(0x00ffffff));
 246         }
 247 
 248         public void move() {
 249             if (x < 10 || x >= AltTabCrashTest.width - 20)
 250                 dx = -dx;
 251             if (y < 10 || y > AltTabCrashTest.height - 20)
 252                 dy = -dy;
 253             x += dx;
 254             y += dy;
 255         }
 256 
 257         public void paint(Graphics g, Color c) {
 258             if (c == null) {
 259                 g.setColor(color);
 260             } else {
 261                 g.setColor(c);
 262             }
 263             g.fillOval(x, y, diameter, diameter);
 264         }
 265 
 266     }
 267 
 268     static class TextBall extends Ball {
 269         String text;
 270         public TextBall(int x, int y, String text) {
 271             super(x, y);
 272             this.text = text;
 273         }
 274 
 275         public void paint(Graphics g, Color c) {
 276             if (c == null) {
 277                 g.setColor(color);
 278             } else {
 279                 g.setColor(c);
 280             }
 281             g.drawString(text, x, y);
 282         }
 283     }
 284 
 285     static class AAOvalBall extends Ball {
 286         public AAOvalBall(int x, int y) {
 287             super(x, y);
 288         }
 289         public void paint(Graphics g, Color c) {
 290             if (c == null) {
 291                 Graphics2D g2d = (Graphics2D)g.create();
 292                 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 293                                      RenderingHints.VALUE_ANTIALIAS_ON);
 294                 g2d.setColor(color);
 295                 g2d.fillOval(x, y, diameter, diameter);
 296             } else {
 297                 g.setColor(c);
 298                 g.fillOval(x-2, y-2, diameter+4, diameter+4);
 299             }
 300         }
 301     }
 302 
 303     static abstract class SpriteBall extends Ball {
 304         Image image;
 305         public SpriteBall(int x, int y) {
 306             super(x, y);
 307             image = createSprite();
 308             Graphics g = image.getGraphics();
 309             g.setColor(color);
 310             g.fillRect(0, 0, image.getWidth(null), image.getHeight(null));
 311         }
 312         public void paint(Graphics g, Color c) {
 313             if (c != null) {
 314                 g.setColor(c);
 315                 g.fillRect(x, y, image.getWidth(null), image.getHeight(null));
 316             } else do {
 317                 validateSprite();
 318                 g.drawImage(image, x, y, null);
 319             } while (renderingIncomplete());
 320         }
 321         public abstract Image createSprite();
 322         public void validateSprite() {}
 323         public boolean renderingIncomplete() { return false; }
 324     }
 325     class VISpriteBall extends SpriteBall {
 326 
 327         public VISpriteBall(int x, int y) {
 328             super(x, y);
 329         }
 330         public boolean renderingIncomplete() {
 331             return ((VolatileImage)image).contentsLost();
 332         }
 333 
 334         public Image createSprite() {
 335             return gd.getDefaultConfiguration().
 336                 createCompatibleVolatileImage(20, 20);
 337         }
 338         public void validateSprite() {
 339             int result =
 340                 ((VolatileImage)image).validate(getGraphicsConfiguration());
 341             if (result == VolatileImage.IMAGE_INCOMPATIBLE) {
 342                 image = createSprite();
 343                 result = VolatileImage.IMAGE_RESTORED;
 344             }
 345             if (result == VolatileImage.IMAGE_RESTORED) {
 346                 Graphics g = image.getGraphics();
 347                 g.setColor(color);
 348                 g.fillRect(0, 0, image.getWidth(null), image.getHeight(null));
 349             }
 350         }
 351     }
 352     class BISpriteBall extends SpriteBall {
 353         public BISpriteBall(int x, int y) {
 354             super(x, y);
 355         }
 356         public Image createSprite() {
 357             return new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB);
 358         }
 359     }
 360 
 361 
 362     public void renderOffscreen() {
 363         Graphics2D g2d = (Graphics2D) vimg.getGraphics();
 364         synchronized (balls) {
 365             for (Ball b : balls) {
 366                 b.paint(g2d, getBackground());
 367                 b.move();
 368                 b.paint(g2d, null);
 369             }
 370         }
 371         g2d.dispose();
 372     }
 373 
 374     public void renderToBS() {
 375         width = getWidth();
 376         height = getHeight();
 377 
 378         do {
 379             Graphics2D g2d = (Graphics2D)bufferStrategy.getDrawGraphics();
 380 
 381             g2d.clearRect(0, 0, width, height);
 382             synchronized (balls) {
 383                 for (Ball b : balls) {
 384                     b.move();
 385                     b.paint(g2d, null);
 386                 }
 387             }
 388             g2d.dispose();
 389         } while (bufferStrategy.contentsLost() ||
 390                 bufferStrategy.contentsRestored());
 391     }
 392 
 393     public void render(Graphics g)  {
 394         do {
 395             height = getBounds().height;
 396             width = getBounds().width;
 397             if (vimg == null) {
 398                 vimg = createVolatileImage(width, height);
 399                 renderOffscreen();
 400             }
 401             int returnCode = vimg.validate(getGraphicsConfiguration());
 402             if (returnCode == VolatileImage.IMAGE_RESTORED) {
 403                 renderOffscreen();
 404             } else if (returnCode == VolatileImage.IMAGE_INCOMPATIBLE) {
 405                 vimg = getGraphicsConfiguration().
 406                     createCompatibleVolatileImage(width, height);
 407                 renderOffscreen();
 408             } else if (returnCode == VolatileImage.IMAGE_OK) {
 409                 renderOffscreen();
 410             }
 411             g.drawImage(vimg, 0, 0, this);
 412         } while (vimg.contentsLost());
 413     }
 414 
 415     public static void main(String args[])  {
 416         for (String arg : args) {
 417             if (arg.equalsIgnoreCase("-auto")) {
 418                 autoMode = true;
 419                 System.err.println("Running in automatic mode using Robot");
 420             } else if (arg.equalsIgnoreCase("-usebs")) {
 421                 useBS = true;
 422                 System.err.println("Using BufferStrategy instead of VI");
 423             } else if (arg.equalsIgnoreCase("-changedm")) {
 424                 changeDM= true;
 425                 System.err.println("The test will change display mode");
 426             } else if (arg.equalsIgnoreCase("-vi")) {
 427                 spriteType = SpriteType.VIMAGES;
 428             } else if (arg.equalsIgnoreCase("-bi")) {
 429                 spriteType = SpriteType.BIMAGES;
 430             } else if (arg.equalsIgnoreCase("-ov")) {
 431                 spriteType = SpriteType.OVALS;
 432             } else if (arg.equalsIgnoreCase("-aaov")) {
 433                 spriteType = SpriteType.AAOVALS;
 434             } else if (arg.equalsIgnoreCase("-tx")) {
 435                 spriteType = SpriteType.TEXT;
 436             } else {
 437                 System.err.println("Usage: AltTabCrashTest [-usebs][-auto]" +
 438                                    "[-changedm][-vi|-bi|-ov|-aaov|-tx]");
 439                 System.err.println(" -usebs: use BufferStrategy instead of VI");
 440                 System.err.println(" -auto: automatically alt+tab in and out" +
 441                                    " of the application ");
 442                 System.err.println(" -changedm: change display mode");
 443                 System.err.println(" -(vi|bi|ov|tx|aaov) : use only VI, BI, " +
 444                                    "text or [AA] [draw]Oval sprites");
 445                 System.exit(0);
 446             }
 447         }
 448         if (spriteType != null) {
 449             System.err.println("The test will only use "+spriteType+" sprites.");
 450         }
 451         new AltTabCrashTest();
 452     }
 453 
 454     private DisplayMode findDisplayMode() {
 455         GraphicsDevice gd = getGraphicsConfiguration().getDevice();
 456         DisplayMode dms[] = gd.getDisplayModes();
 457         DisplayMode currentDM = gd.getDisplayMode();
 458         for (DisplayMode dm : dms) {
 459             if (dm.getBitDepth() > 8 &&
 460                 dm.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
 461                 dm.getBitDepth() != currentDM.getBitDepth() &&
 462                 dm.getWidth() == currentDM.getWidth() &&
 463                 dm.getHeight() == currentDM.getHeight())
 464             {
 465                 // found a mode which has the same dimensions but different
 466                 // depth
 467                 return dm;
 468             }
 469             if (dm.getBitDepth() == DisplayMode.BIT_DEPTH_MULTI &&
 470                 (dm.getWidth() != currentDM.getWidth() ||
 471                  dm.getHeight() != currentDM.getHeight()))
 472             {
 473                 // found a mode which has the same depth but different
 474                 // dimensions
 475                 return dm;
 476             }
 477         }
 478 
 479         return null;
 480     }
 481 }