1 /* 2 * Copyright (c) 2010, 2013, 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 com.sun.javafx.application; 27 28 import com.sun.javafx.PlatformUtil; 29 import com.sun.javafx.css.StyleManager; 30 import com.sun.javafx.runtime.SystemProperties; 31 32 import java.lang.reflect.InvocationTargetException; 33 import java.lang.reflect.Method; 34 import java.security.AccessControlContext; 35 import java.util.List; 36 import java.util.Set; 37 import java.util.concurrent.CopyOnWriteArraySet; 38 import java.util.concurrent.CountDownLatch; 39 import java.util.concurrent.atomic.AtomicBoolean; 40 import java.util.concurrent.atomic.AtomicInteger; 41 42 import javafx.application.Application; 43 import javafx.application.ConditionalFeature; 44 45 import com.sun.javafx.tk.TKListener; 46 import com.sun.javafx.tk.TKStage; 47 import com.sun.javafx.tk.Toolkit; 48 import javafx.application.Platform; 49 import javafx.scene.Scene; 50 51 import java.security.AccessController; 52 import java.security.AllPermission; 53 import java.security.PrivilegedAction; 54 55 public class PlatformImpl { 56 57 private static AtomicBoolean initialized = new AtomicBoolean(false); 58 private static AtomicBoolean platformExit = new AtomicBoolean(false); 59 private static AtomicBoolean toolkitExit = new AtomicBoolean(false); 60 private static CountDownLatch startupLatch = new CountDownLatch(1); 61 private static AtomicBoolean listenersRegistered = new AtomicBoolean(false); 62 private static TKListener toolkitListener = null; 63 private static volatile boolean implicitExit = true; 64 private static boolean taskbarApplication = true; 65 private static boolean contextual2DNavigation; 66 private static AtomicInteger pendingRunnables = new AtomicInteger(0); 67 private static AtomicInteger numWindows = new AtomicInteger(0); 68 private static volatile boolean firstWindowShown = false; 69 private static volatile boolean lastWindowClosed = false; 70 private static AtomicBoolean reallyIdle = new AtomicBoolean(false); 71 private static Set<FinishListener> finishListeners = 72 new CopyOnWriteArraySet<FinishListener>(); 73 private final static Object runLaterLock = new Object(); 74 private static Boolean isGraphicsSupported; 75 private static Boolean isControlsSupported; 76 private static Boolean isMediaSupported; 77 private static Boolean isWebSupported; 78 private static Boolean isSWTSupported; 79 private static Boolean isSwingSupported; 80 private static Boolean isFXMLSupported; 81 private static Boolean hasTwoLevelFocus; 82 private static Boolean hasVirtualKeyboard; 83 private static Boolean hasTouch; 84 private static Boolean hasMultiTouch; 85 private static Boolean hasPointer; 86 private static boolean isThreadMerged = false; 87 88 /** 89 * Set a flag indicating whether this application should show up in the 90 * task bar. The default value is true. 91 * 92 * @param taskbarApplication the new value of this attribute 93 */ 94 public static void setTaskbarApplication(boolean taskbarApplication) { 95 PlatformImpl.taskbarApplication = taskbarApplication; 96 } 97 98 /** 99 * Returns the current value of the taskBarApplication flag. 100 * 101 * @return the current state of the flag. 102 */ 103 public static boolean isTaskbarApplication() { 104 return taskbarApplication; 105 } 106 107 /** 108 * Sets the name of the this application based on the Application class. 109 * This method is called by the launcher or by the deploy code, and is not 110 * called from the FX Application Thread, so we need to do it in a runLater. 111 * We do not need to wait for the result since it will complete before the 112 * Application start() method is called regardless. 113 * 114 * @param appClass the Application class. 115 */ 116 public static void setApplicationName(final Class appClass) { 117 runLater(() -> com.sun.glass.ui.Application.GetApplication().setName(appClass.getName())); 118 } 119 120 /** 121 * Return whether or not focus navigation between controls is context- 122 * sensitive. 123 * @return true if the context-sensitive algorithm for focus navigation is 124 * used 125 */ 126 public static boolean isContextual2DNavigation() { 127 return contextual2DNavigation; 128 } 129 130 /** 131 * This method is invoked typically on the main thread. At this point, 132 * the JavaFX Application Thread has not been started. Any attempt 133 * to call startup twice results in an exception. 134 * @param r 135 */ 136 public static void startup(final Runnable r) { 137 138 // NOTE: if we ever support re-launching an application and/or 139 // launching a second application in the same VM/classloader 140 // this will need to be changed. 141 if (platformExit.get()) { 142 throw new IllegalStateException("Platform.exit has been called"); 143 } 144 145 if (initialized.getAndSet(true)) { 146 // If we've already initialized, just put the runnable on the queue. 147 runLater(r); 148 return; 149 } 150 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 151 contextual2DNavigation = Boolean.getBoolean( 152 "com.sun.javafx.isContextual2DNavigation"); 153 String s = System.getProperty("com.sun.javafx.twoLevelFocus"); 154 if (s != null) { 155 hasTwoLevelFocus = Boolean.valueOf(s); 156 } 157 s = System.getProperty("com.sun.javafx.virtualKeyboard"); 158 if (s != null) { 159 if (s.equalsIgnoreCase("none")) { 160 hasVirtualKeyboard = false; 161 } else if (s.equalsIgnoreCase("javafx")) { 162 hasVirtualKeyboard = true; 163 } else if (s.equalsIgnoreCase("native")) { 164 hasVirtualKeyboard = true; 165 } 166 } 167 s = System.getProperty("com.sun.javafx.touch"); 168 if (s != null) { 169 hasTouch = Boolean.valueOf(s); 170 } 171 s = System.getProperty("com.sun.javafx.multiTouch"); 172 if (s != null) { 173 hasMultiTouch = Boolean.valueOf(s); 174 } 175 s = System.getProperty("com.sun.javafx.pointer"); 176 if (s != null) { 177 hasPointer = Boolean.valueOf(s); 178 } 179 s = System.getProperty("javafx.embed.singleThread"); 180 if (s != null) { 181 isThreadMerged = Boolean.valueOf(s); 182 } 183 return null; 184 }); 185 186 if (!taskbarApplication) { 187 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 188 System.setProperty("glass.taskbarApplication", "false"); 189 return null; 190 }); 191 } 192 193 // Create Toolkit listener and register it with the Toolkit. 194 // Call notifyFinishListeners when we get notified. 195 toolkitListener = new TKListener() { 196 @Override public void changedTopLevelWindows(List<TKStage> windows) { 197 numWindows.set(windows.size()); 198 checkIdle(); 199 } 200 201 @Override 202 public void exitedLastNestedLoop() { 203 checkIdle(); 204 } 205 }; 206 Toolkit.getToolkit().addTkListener(toolkitListener); 207 208 Toolkit.getToolkit().startup(() -> { 209 startupLatch.countDown(); 210 r.run(); 211 }); 212 213 //Initialize the thread merging mechanism 214 if (isThreadMerged) { 215 installFwEventQueue(); 216 } 217 } 218 219 private static void installFwEventQueue() { 220 invokeSwingFXUtilsMethod("installFwEventQueue"); 221 } 222 223 private static void removeFwEventQueue() { 224 invokeSwingFXUtilsMethod("removeFwEventQueue"); 225 } 226 227 private static void invokeSwingFXUtilsMethod(final String methodName) { 228 //Use reflection in case we are running compact profile 229 try { 230 Class swingFXUtilsClass = Class.forName("javafx.embed.swing.SwingFXUtils"); 231 Method installFwEventQueue = swingFXUtilsClass.getMethod(methodName); 232 233 waitForStart(); 234 installFwEventQueue.invoke(null); 235 236 } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) { 237 throw new RuntimeException("Property javafx.embed.singleThread is not supported"); 238 } catch (InvocationTargetException e) { 239 throw new RuntimeException(e); 240 } 241 } 242 243 private static void waitForStart() { 244 // If the startup runnable has not yet been called, then wait it. 245 // Note that we check the count before calling await() to avoid 246 // the try/catch which is unnecessary after startup. 247 if (startupLatch.getCount() > 0) { 248 try { 249 startupLatch.await(); 250 } catch (InterruptedException ex) { 251 ex.printStackTrace(); 252 } 253 } 254 } 255 256 public static boolean isFxApplicationThread() { 257 return Toolkit.getToolkit().isFxUserThread(); 258 } 259 260 public static void runLater(final Runnable r) { 261 runLater(r, false); 262 } 263 264 private static void runLater(final Runnable r, boolean exiting) { 265 if (!initialized.get()) { 266 throw new IllegalStateException("Toolkit not initialized"); 267 } 268 269 pendingRunnables.incrementAndGet(); 270 waitForStart(); 271 272 if (SystemProperties.isDebug()) { 273 Toolkit.getToolkit().pauseCurrentThread(); 274 } 275 276 synchronized (runLaterLock) { 277 if (!exiting && toolkitExit.get()) { 278 // Don't schedule a runnable after we have exited the toolkit 279 pendingRunnables.decrementAndGet(); 280 return; 281 } 282 283 final AccessControlContext acc = AccessController.getContext(); 284 // Don't catch exceptions, they are handled by Toolkit.defer() 285 Toolkit.getToolkit().defer(() -> { 286 try { 287 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 288 r.run(); 289 return null; 290 }, acc); 291 } finally { 292 pendingRunnables.decrementAndGet(); 293 checkIdle(); 294 } 295 }); 296 } 297 } 298 299 public static void runAndWait(final Runnable r) { 300 runAndWait(r, false); 301 } 302 303 private static void runAndWait(final Runnable r, boolean exiting) { 304 if (SystemProperties.isDebug()) { 305 Toolkit.getToolkit().pauseCurrentThread(); 306 } 307 308 if (isFxApplicationThread()) { 309 try { 310 r.run(); 311 } catch (Throwable t) { 312 System.err.println("Exception in runnable"); 313 t.printStackTrace(); 314 } 315 } else { 316 final CountDownLatch doneLatch = new CountDownLatch(1); 317 runLater(() -> { 318 try { 319 r.run(); 320 } finally { 321 doneLatch.countDown(); 322 } 323 }, exiting); 324 325 if (!exiting && toolkitExit.get()) { 326 throw new IllegalStateException("Toolkit has exited"); 327 } 328 329 try { 330 doneLatch.await(); 331 } catch (InterruptedException ex) { 332 ex.printStackTrace(); 333 } 334 } 335 } 336 337 public static void setImplicitExit(boolean implicitExit) { 338 PlatformImpl.implicitExit = implicitExit; 339 checkIdle(); 340 } 341 342 public static boolean isImplicitExit() { 343 return implicitExit; 344 } 345 346 public static void addListener(FinishListener l) { 347 listenersRegistered.set(true); 348 finishListeners.add(l); 349 } 350 351 public static void removeListener(FinishListener l) { 352 finishListeners.remove(l); 353 listenersRegistered.set(!finishListeners.isEmpty()); 354 if (!listenersRegistered.get()) { 355 checkIdle(); 356 } 357 } 358 359 private static void notifyFinishListeners(boolean exitCalled) { 360 // Notify listeners if any are registered, else exit directly 361 if (listenersRegistered.get()) { 362 for (FinishListener l : finishListeners) { 363 if (exitCalled) { 364 l.exitCalled(); 365 } else { 366 l.idle(implicitExit); 367 } 368 } 369 } else if (implicitExit || platformExit.get()) { 370 tkExit(); 371 } 372 } 373 374 // Check for idle, meaning the last top-level window has been closed and 375 // there are no pending Runnables waiting to be run. 376 private static void checkIdle() { 377 // If we aren't initialized yet, then this method is a no-op. 378 if (!initialized.get()) { 379 return; 380 } 381 382 if (!isFxApplicationThread()) { 383 // Add a dummy runnable to the runLater queue, which will then call 384 // checkIdle() on the FX application thread. 385 runLater(() -> { 386 }); 387 return; 388 } 389 390 boolean doNotify = false; 391 392 synchronized (PlatformImpl.class) { 393 int numWin = numWindows.get(); 394 if (numWin > 0) { 395 firstWindowShown = true; 396 lastWindowClosed = false; 397 reallyIdle.set(false); 398 } else if (numWin == 0 && firstWindowShown) { 399 lastWindowClosed = true; 400 } 401 402 // In case there is an event in process, allow for it to show 403 // another window. If no new window is shown before all pending 404 // runnables (including this one) are done and there is no running 405 // nested loops, then we will shutdown. 406 if (lastWindowClosed && pendingRunnables.get() == 0 407 && (toolkitExit.get() || !Toolkit.getToolkit().isNestedLoopRunning())) { 408 // System.err.println("Last window closed and no pending runnables"); 409 if (reallyIdle.getAndSet(true)) { 410 // System.err.println("Really idle now"); 411 doNotify = true; 412 lastWindowClosed = false; 413 } else { 414 // System.err.println("Queuing up a dummy idle check runnable"); 415 runLater(() -> { 416 // System.err.println("Dummy runnable"); 417 }); 418 } 419 } 420 } 421 422 if (doNotify) { 423 notifyFinishListeners(false); 424 } 425 } 426 427 // package scope method for testing 428 private static final CountDownLatch platformExitLatch = new CountDownLatch(1); 429 static CountDownLatch test_getPlatformExitLatch() { 430 return platformExitLatch; 431 } 432 433 public static void tkExit() { 434 if (toolkitExit.getAndSet(true)) { 435 return; 436 } 437 438 if (initialized.get()) { 439 // Always call toolkit exit on FX app thread 440 // System.err.println("PlatformImpl.tkExit: scheduling Toolkit.exit"); 441 PlatformImpl.runAndWait(() -> Toolkit.getToolkit().exit(), true); 442 443 if (isThreadMerged) { 444 removeFwEventQueue(); 445 } 446 447 Toolkit.getToolkit().removeTkListener(toolkitListener); 448 toolkitListener = null; 449 platformExitLatch.countDown(); 450 } 451 } 452 453 public static void exit() { 454 platformExit.set(true); 455 notifyFinishListeners(true); 456 } 457 458 private static Boolean checkForClass(String classname) { 459 try { 460 Class.forName(classname, false, PlatformImpl.class.getClassLoader()); 461 return Boolean.TRUE; 462 } catch (ClassNotFoundException cnfe) { 463 return Boolean.FALSE; 464 } 465 } 466 467 public static boolean isSupported(ConditionalFeature feature) { 468 final boolean supported = isSupportedImpl(feature); 469 if (supported && (feature == ConditionalFeature.TRANSPARENT_WINDOW)) { 470 // some features require the application to have the corresponding 471 // permissions, if the application doesn't have them, the platform 472 // will behave as if the feature wasn't supported 473 final SecurityManager securityManager = 474 System.getSecurityManager(); 475 if (securityManager != null) { 476 try { 477 securityManager.checkPermission(new AllPermission()); 478 } catch (final SecurityException e) { 479 return false; 480 } 481 } 482 483 return true; 484 } 485 486 return supported; 487 } 488 489 public static interface FinishListener { 490 public void idle(boolean implicitExit); 491 public void exitCalled(); 492 } 493 494 /** 495 * Set the platform user agent stylesheet to the default. 496 */ 497 public static void setDefaultPlatformUserAgentStylesheet() { 498 setPlatformUserAgentStylesheet(Application.STYLESHEET_MODENA); 499 } 500 501 private static boolean isModena = false; 502 private static boolean isCaspian = false; 503 504 /** 505 * Current Platform User Agent Stylesheet is Modena. 506 * 507 * Note: Please think hard before using this as we really want to avoid special cases in the platform for specific 508 * themes. This was added to allow tempory work arounds in the platform for bugs. 509 * 510 * @return true if using modena stylesheet 511 */ 512 public static boolean isModena() { 513 return isModena; 514 } 515 516 /** 517 * Current Platform User Agent Stylesheet is Caspian. 518 * 519 * Note: Please think hard before using this as we really want to avoid special cases in the platform for specific 520 * themes. This was added to allow tempory work arounds in the platform for bugs. 521 * 522 * @return true if using caspian stylesheet 523 */ 524 public static boolean isCaspian() { 525 return isCaspian; 526 } 527 528 /** 529 * Set the platform user agent stylesheet to the given URL. This method has special handling for platform theme 530 * name constants. 531 */ 532 public static void setPlatformUserAgentStylesheet(final String stylesheetUrl) { 533 if (isFxApplicationThread()) { 534 _setPlatformUserAgentStylesheet(stylesheetUrl); 535 } else { 536 runLater(() -> _setPlatformUserAgentStylesheet(stylesheetUrl)); 537 } 538 } 539 540 private static void _setPlatformUserAgentStylesheet(String stylesheetUrl) { 541 isModena = isCaspian = false; 542 // check for command line override 543 final String overrideStylesheetUrl = AccessController.doPrivileged( 544 (PrivilegedAction<String>) () -> System.getProperty("javafx.userAgentStylesheetUrl")); 545 546 if (overrideStylesheetUrl != null) { 547 stylesheetUrl = overrideStylesheetUrl; 548 } 549 550 // check for named theme constants for modena and caspian 551 if (Application.STYLESHEET_CASPIAN.equalsIgnoreCase(stylesheetUrl)) { 552 isCaspian = true; 553 AccessController.doPrivileged( 554 (PrivilegedAction) () -> { 555 StyleManager.getInstance().setDefaultUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/caspian.css"); 556 557 if (isSupported(ConditionalFeature.INPUT_TOUCH)) { 558 StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/embedded.css"); 559 if (com.sun.javafx.Utils.isQVGAScreen()) { 560 StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/embedded-qvga.css"); 561 } 562 if (PlatformUtil.isAndroid()) { 563 StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/android.css"); 564 } 565 } 566 567 if (isSupported(ConditionalFeature.TWO_LEVEL_FOCUS)) { 568 StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/two-level-focus.css"); 569 } 570 571 if (isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { 572 StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/fxvk.css"); 573 } 574 return null; 575 } 576 ); 577 } else if (Application.STYLESHEET_MODENA.equalsIgnoreCase(stylesheetUrl)) { 578 isModena = true; 579 AccessController.doPrivileged( 580 (PrivilegedAction) () -> { 581 StyleManager.getInstance().setDefaultUserAgentStylesheet("com/sun/javafx/scene/control/skin/modena/modena.css"); 582 583 if (isSupported(ConditionalFeature.INPUT_TOUCH)) { 584 StyleManager.getInstance().addUserAgentStylesheet( 585 "com/sun/javafx/scene/control/skin/modena/touch.css"); 586 } 587 // when running on embedded add a extra stylesheet to tune performance of modena theme 588 if (PlatformUtil.isEmbedded()) { 589 StyleManager.getInstance().addUserAgentStylesheet( 590 "com/sun/javafx/scene/control/skin/modena/modena-embedded-performance.css"); 591 } 592 if (PlatformUtil.isAndroid()) { 593 StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/modena/android.css"); 594 } 595 596 if (isSupported(ConditionalFeature.TWO_LEVEL_FOCUS)) { 597 StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/modena/two-level-focus.css"); 598 } 599 600 if (isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { 601 StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/fxvk.css"); 602 } 603 return null; 604 } 605 ); 606 607 // check to see if there is an override to enable a high-contrast theme 608 final String highContrastName = AccessController.doPrivileged( 609 (PrivilegedAction<String>) () -> System.getProperty("com.sun.javafx.highContrastTheme")); 610 if (highContrastName != null) { 611 switch (highContrastName.toUpperCase()) { 612 case "BLACKONWHITE": StyleManager.getInstance().addUserAgentStylesheet( 613 "com/sun/javafx/scene/control/skin/modena/blackOnWhite.css"); 614 break; 615 case "WHITEONBLACK": StyleManager.getInstance().addUserAgentStylesheet( 616 "com/sun/javafx/scene/control/skin/modena/whiteOnBlack.css"); 617 break; 618 case "YELLOWONBLACK": StyleManager.getInstance().addUserAgentStylesheet( 619 "com/sun/javafx/scene/control/skin/modena/yellowOnBlack.css"); 620 break; 621 } 622 } 623 } else { 624 StyleManager.getInstance().setDefaultUserAgentStylesheet(stylesheetUrl); 625 } 626 } 627 628 public static void addNoTransparencyStylesheetToScene(final Scene scene) { 629 if (PlatformImpl.isCaspian()) { 630 AccessController.doPrivileged((PrivilegedAction) () -> { 631 StyleManager.getInstance().addUserAgentStylesheet(scene, 632 "com/sun/javafx/scene/control/skin/caspian/caspian-no-transparency.css"); 633 return null; 634 }); 635 } else if (PlatformImpl.isModena()) { 636 AccessController.doPrivileged((PrivilegedAction) () -> { 637 StyleManager.getInstance().addUserAgentStylesheet(scene, 638 "com/sun/javafx/scene/control/skin/modena/modena-no-transparency.css"); 639 return null; 640 }); 641 } 642 } 643 644 private static boolean isSupportedImpl(ConditionalFeature feature) { 645 switch (feature) { 646 case GRAPHICS: 647 if (isGraphicsSupported == null) { 648 isGraphicsSupported = checkForClass("javafx.stage.Stage"); 649 } 650 return isGraphicsSupported; 651 case CONTROLS: 652 if (isControlsSupported == null) { 653 isControlsSupported = checkForClass( 654 "javafx.scene.control.Control"); 655 } 656 return isControlsSupported; 657 case MEDIA: 658 if (isMediaSupported == null) { 659 isMediaSupported = checkForClass( 660 "javafx.scene.media.MediaView"); 661 } 662 return isMediaSupported; 663 case WEB: 664 if (isWebSupported == null) { 665 isWebSupported = checkForClass("javafx.scene.web.WebView"); 666 } 667 return isWebSupported; 668 case SWT: 669 if (isSWTSupported == null) { 670 isSWTSupported = checkForClass("javafx.embed.swt.FXCanvas"); 671 } 672 return isSWTSupported; 673 case SWING: 674 if (isSwingSupported == null) { 675 isSwingSupported = 676 // check for JComponent first, it may not be present 677 checkForClass("javax.swing.JComponent") && 678 checkForClass("javafx.embed.swing.JFXPanel"); 679 } 680 return isSwingSupported; 681 case FXML: 682 if (isFXMLSupported == null) { 683 isFXMLSupported = checkForClass("javafx.fxml.FXMLLoader") 684 && checkForClass("javax.xml.stream.XMLInputFactory"); 685 } 686 return isFXMLSupported; 687 case TWO_LEVEL_FOCUS: 688 if (hasTwoLevelFocus == null) { 689 return Toolkit.getToolkit().isSupported(feature); 690 } 691 return hasTwoLevelFocus; 692 case VIRTUAL_KEYBOARD: 693 if (hasVirtualKeyboard == null) { 694 return Toolkit.getToolkit().isSupported(feature); 695 } 696 return hasVirtualKeyboard; 697 case INPUT_TOUCH: 698 if (hasTouch == null) { 699 return Toolkit.getToolkit().isSupported(feature); 700 } 701 return hasTouch; 702 case INPUT_MULTITOUCH: 703 if (hasMultiTouch == null) { 704 return Toolkit.getToolkit().isSupported(feature); 705 } 706 return hasMultiTouch; 707 case INPUT_POINTER: 708 if (hasPointer == null) { 709 return Toolkit.getToolkit().isSupported(feature); 710 } 711 return hasPointer; 712 default: 713 return Toolkit.getToolkit().isSupported(feature); 714 } 715 } 716 }