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