1 /*
   2  * Copyright (c) 2005, 2015, 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 sun.awt.X11;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.awt.peer.TrayIconPeer;
  31 import sun.awt.*;
  32 import java.awt.image.*;
  33 import java.security.AccessController;
  34 import java.security.PrivilegedAction;
  35 import java.lang.reflect.InvocationTargetException;
  36 import sun.util.logging.PlatformLogger;
  37 
  38 public class XTrayIconPeer implements TrayIconPeer,
  39        InfoWindow.Balloon.LiveArguments,
  40        InfoWindow.Tooltip.LiveArguments
  41 {
  42     private static final PlatformLogger ctrLog = PlatformLogger.getLogger("sun.awt.X11.XTrayIconPeer.centering");
  43 
  44     TrayIcon target;
  45     TrayIconEventProxy eventProxy;
  46     XTrayIconEmbeddedFrame eframe;
  47     TrayIconCanvas canvas;
  48     InfoWindow.Balloon balloon;
  49     InfoWindow.Tooltip tooltip;
  50     PopupMenu popup;
  51     String tooltipString;
  52     boolean isTrayIconDisplayed;
  53     long eframeParentID;
  54     final XEventDispatcher parentXED, eframeXED;
  55 
  56     static final XEventDispatcher dummyXED = new XEventDispatcher() {
  57             public void dispatchEvent(XEvent ev) {}
  58         };
  59 
  60     volatile boolean isDisposed;
  61 
  62     boolean isParentWindowLocated;
  63     int old_x, old_y;
  64     int ex_width, ex_height;
  65 
  66     static final int TRAY_ICON_WIDTH = 24;
  67     static final int TRAY_ICON_HEIGHT = 24;
  68 
  69     XTrayIconPeer(TrayIcon target)
  70       throws AWTException
  71     {
  72         this.target = target;
  73 
  74         eventProxy = new TrayIconEventProxy(this);
  75 
  76         canvas = new TrayIconCanvas(target, TRAY_ICON_WIDTH, TRAY_ICON_HEIGHT);
  77 
  78         eframe = new XTrayIconEmbeddedFrame();
  79 
  80         eframe.setSize(TRAY_ICON_WIDTH, TRAY_ICON_HEIGHT);
  81         eframe.add(canvas);
  82 
  83         // Fix for 6317038: as EmbeddedFrame is instance of Frame, it is blocked
  84         // by modal dialogs, but in the case of TrayIcon it shouldn't. So we
  85         // set ModalExclusion property on it.
  86         AccessController.doPrivileged(new PrivilegedAction<Object>() {
  87             public Object run() {
  88                 eframe.setModalExclusionType(Dialog.ModalExclusionType.TOOLKIT_EXCLUDE);
  89                 return null;
  90             }
  91         });
  92 
  93 
  94         if (XWM.getWMID() != XWM.METACITY_WM) {
  95             parentXED = dummyXED; // We don't like to leave it 'null'.
  96         } else {
  97             parentXED = new XEventDispatcher() {
  98                 // It's executed under AWTLock.
  99                 public void dispatchEvent(XEvent ev) {
 100                     if (isDisposed() || ev.get_type() != XConstants.ConfigureNotify) {
 101                         return;
 102                     }
 103 
 104                     XConfigureEvent ce = ev.get_xconfigure();
 105 
 106                     if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 107                         ctrLog.fine("ConfigureNotify on parent of {0}: {1}x{2}+{3}+{4} (old: {5}+{6})",
 108                                 XTrayIconPeer.this, ce.get_width(), ce.get_height(),
 109                                 ce.get_x(), ce.get_y(), old_x, old_y);
 110                     }
 111 
 112                     // A workaround for Gnome/Metacity (it doesn't affect the behaviour on KDE).
 113                     // On Metacity the EmbeddedFrame's parent window bounds are larger
 114                     // than TrayIcon size required (that is we need a square but a rectangle
 115                     // is provided by the Panel Notification Area). The parent's background color
 116                     // differs from the Panel's one. To hide the background we resize parent
 117                     // window so that it fits the EmbeddedFrame.
 118                     // However due to resizing the parent window it loses centering in the Panel.
 119                     // We center it when discovering that some of its side is of size greater
 120                     // than the fixed value. Centering is being done by "X" (when the parent's width
 121                     // is greater) and by "Y" (when the parent's height is greater).
 122 
 123                     // Actually we need this workaround until we could detect taskbar color.
 124 
 125                     if (ce.get_height() != TRAY_ICON_HEIGHT && ce.get_width() != TRAY_ICON_WIDTH) {
 126 
 127                         // If both the height and the width differ from the fixed size then WM
 128                         // must level at least one side to the fixed size. For some reason it may take
 129                         // a few hops (even after reparenting) and we have to skip the intermediate ones.
 130                         if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 131                             ctrLog.fine("ConfigureNotify on parent of {0}. Skipping as intermediate resizing.",
 132                                     XTrayIconPeer.this);
 133                         }
 134                         return;
 135 
 136                     } else if (ce.get_height() > TRAY_ICON_HEIGHT) {
 137 
 138                         if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 139                             ctrLog.fine("ConfigureNotify on parent of {0}. Centering by \"Y\".",
 140                                     XTrayIconPeer.this);
 141                         }
 142 
 143                         XlibWrapper.XMoveResizeWindow(XToolkit.getDisplay(), eframeParentID,
 144                                                       ce.get_x(),
 145                                                       ce.get_y()+ce.get_height()/2-TRAY_ICON_HEIGHT/2,
 146                                                       TRAY_ICON_WIDTH,
 147                                                       TRAY_ICON_HEIGHT);
 148                         ex_height = ce.get_height();
 149                         ex_width = 0;
 150 
 151                     } else if (ce.get_width() > TRAY_ICON_WIDTH) {
 152 
 153                         if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 154                             ctrLog.fine("ConfigureNotify on parent of {0}. Centering by \"X\".",
 155                                     XTrayIconPeer.this);
 156                         }
 157 
 158                         XlibWrapper.XMoveResizeWindow(XToolkit.getDisplay(), eframeParentID,
 159                                                       ce.get_x()+ce.get_width()/2 - TRAY_ICON_WIDTH/2,
 160                                                       ce.get_y(),
 161                                                       TRAY_ICON_WIDTH,
 162                                                       TRAY_ICON_HEIGHT);
 163                         ex_width = ce.get_width();
 164                         ex_height = 0;
 165 
 166                     } else if (isParentWindowLocated && ce.get_x() != old_x && ce.get_y() != old_y) {
 167                         // If moving by both "X" and "Y".
 168                         // When some tray icon gets removed from the tray, a Java icon may be repositioned.
 169                         // In this case the parent window also lose centering. We have to restore it.
 170 
 171                         if (ex_height != 0) {
 172 
 173                             if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 174                                 ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Centering by \"Y\".",
 175                                         XTrayIconPeer.this);
 176                             }
 177 
 178                             XlibWrapper.XMoveWindow(XToolkit.getDisplay(), eframeParentID,
 179                                                     ce.get_x(),
 180                                                     ce.get_y() + ex_height/2 - TRAY_ICON_HEIGHT/2);
 181 
 182                         } else if (ex_width != 0) {
 183 
 184                             if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 185                                 ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Centering by \"X\".",
 186                                         XTrayIconPeer.this);
 187                             }
 188 
 189                             XlibWrapper.XMoveWindow(XToolkit.getDisplay(), eframeParentID,
 190                                                     ce.get_x() + ex_width/2 - TRAY_ICON_WIDTH/2,
 191                                                     ce.get_y());
 192                         } else {
 193                             if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 194                                 ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Skipping.",
 195                                         XTrayIconPeer.this);
 196                             }
 197                         }
 198                     }
 199                     old_x = ce.get_x();
 200                     old_y = ce.get_y();
 201                     isParentWindowLocated = true;
 202                 }
 203             };
 204         }
 205         eframeXED = new XEventDispatcher() {
 206                 // It's executed under AWTLock.
 207                 XTrayIconPeer xtiPeer = XTrayIconPeer.this;
 208 
 209                 public void dispatchEvent(XEvent ev) {
 210                     if (isDisposed() || ev.get_type() != XConstants.ReparentNotify) {
 211                         return;
 212                     }
 213 
 214                     XReparentEvent re = ev.get_xreparent();
 215                     eframeParentID = re.get_parent();
 216 
 217                     if (eframeParentID == XToolkit.getDefaultRootWindow()) {
 218 
 219                         if (isTrayIconDisplayed) { // most likely Notification Area was removed
 220                             SunToolkit.executeOnEventHandlerThread(xtiPeer.target, new Runnable() {
 221                                     public void run() {
 222                                         SystemTray.getSystemTray().remove(xtiPeer.target);
 223                                     }
 224                                 });
 225                         }
 226                         return;
 227                     }
 228 
 229                     if (!isTrayIconDisplayed) {
 230                         addXED(eframeParentID, parentXED, XConstants.StructureNotifyMask);
 231 
 232                         isTrayIconDisplayed = true;
 233                         XToolkit.awtLockNotifyAll();
 234                     }
 235                 }
 236             };
 237 
 238         addXED(getWindow(), eframeXED, XConstants.StructureNotifyMask);
 239 
 240         XSystemTrayPeer.getPeerInstance().addTrayIcon(this); // throws AWTException
 241 
 242         // Wait till the EmbeddedFrame is reparented
 243         long start = System.currentTimeMillis();
 244         final long PERIOD = XToolkit.getTrayIconDisplayTimeout();
 245         XToolkit.awtLock();
 246         try {
 247             while (!isTrayIconDisplayed) {
 248                 try {
 249                     XToolkit.awtLockWait(PERIOD);
 250                 } catch (InterruptedException e) {
 251                     break;
 252                 }
 253                 if (System.currentTimeMillis() - start > PERIOD) {
 254                     break;
 255                 }
 256             }
 257         } finally {
 258             XToolkit.awtUnlock();
 259         }
 260 
 261         // This is unlikely to happen.
 262         if (!isTrayIconDisplayed || eframeParentID == 0 ||
 263             eframeParentID == XToolkit.getDefaultRootWindow())
 264         {
 265             throw new AWTException("TrayIcon couldn't be displayed.");
 266         }
 267 
 268         eframe.setVisible(true);
 269         updateImage();
 270 
 271         balloon = new InfoWindow.Balloon(eframe, target, this);
 272         tooltip = new InfoWindow.Tooltip(eframe, target, this);
 273 
 274         addListeners();
 275     }
 276 
 277     public void dispose() {
 278         if (SunToolkit.isDispatchThreadForAppContext(target)) {
 279             disposeOnEDT();
 280         } else {
 281             try {
 282                 SunToolkit.executeOnEDTAndWait(target, new Runnable() {
 283                         public void run() {
 284                             disposeOnEDT();
 285                         }
 286                     });
 287             } catch (InterruptedException ie) {
 288             } catch (InvocationTargetException ite) {}
 289         }
 290     }
 291 
 292     private void disposeOnEDT() {
 293         // All actions that is to be synchronized with disposal
 294         // should be executed either under AWTLock, or on EDT.
 295         // isDisposed value must be checked.
 296         XToolkit.awtLock();
 297         isDisposed = true;
 298         XToolkit.awtUnlock();
 299 
 300         removeXED(getWindow(), eframeXED);
 301         removeXED(eframeParentID, parentXED);
 302         removeListeners();
 303         eframe.realDispose();
 304         balloon.dispose();
 305         tooltip.dispose();
 306         isTrayIconDisplayed = false;
 307         canvas.dispose();
 308         canvas = null;
 309         popup = null;
 310         balloon = null;
 311         tooltip = null;
 312         XToolkit.targetDisposedPeer(target, this);
 313         target = null;
 314         eframe = null;
 315     }
 316 
 317     public static void suppressWarningString(Window w) {
 318         AWTAccessor.getWindowAccessor().setTrayIconWindow(w, true);
 319     }
 320 
 321     public void setToolTip(String tooltip) {
 322         tooltipString = tooltip;
 323     }
 324 
 325     public String getTooltipString() {
 326         return tooltipString;
 327     }
 328 
 329     public void updateImage() {
 330         Runnable r = new Runnable() {
 331                 public void run() {
 332                     canvas.updateImage(target.getImage());
 333                 }
 334             };
 335 
 336         if (!SunToolkit.isDispatchThreadForAppContext(target)) {
 337             SunToolkit.executeOnEventHandlerThread(target, r);
 338         } else {
 339             r.run();
 340         }
 341     }
 342 
 343     public void displayMessage(String caption, String text, String messageType) {
 344         Point loc = getLocationOnScreen();
 345         Rectangle screen = eframe.getGraphicsConfiguration().getBounds();
 346 
 347         // Check if the tray icon is in the bounds of a screen.
 348         if (!(loc.x < screen.x || loc.x >= screen.x + screen.width ||
 349               loc.y < screen.y || loc.y >= screen.y + screen.height))
 350         {
 351             balloon.display(caption, text, messageType);
 352         }
 353     }
 354 
 355     // It's synchronized with disposal by EDT.
 356     public void showPopupMenu(int x, int y) {
 357         if (isDisposed())
 358             return;
 359 
 360         assert SunToolkit.isDispatchThreadForAppContext(target);
 361 
 362         PopupMenu newPopup = target.getPopupMenu();
 363         if (popup != newPopup) {
 364             if (popup != null) {
 365                 eframe.remove(popup);
 366             }
 367             if (newPopup != null) {
 368                 eframe.add(newPopup);
 369             }
 370             popup = newPopup;
 371         }
 372 
 373         if (popup != null) {
 374             final XBaseWindow peer = AWTAccessor.getComponentAccessor()
 375                                                 .getPeer(eframe);
 376             Point loc = peer.toLocal(new Point(x, y));
 377             popup.show(eframe, loc.x, loc.y);
 378         }
 379     }
 380 
 381 
 382     // ******************************************************************
 383     // ******************************************************************
 384 
 385 
 386     private void addXED(long window, XEventDispatcher xed, long mask) {
 387         if (window == 0) {
 388             return;
 389         }
 390         XToolkit.awtLock();
 391         try {
 392             XlibWrapper.XSelectInput(XToolkit.getDisplay(), window, mask);
 393         } finally {
 394             XToolkit.awtUnlock();
 395         }
 396         XToolkit.addEventDispatcher(window, xed);
 397     }
 398 
 399     private void removeXED(long window, XEventDispatcher xed) {
 400         if (window == 0) {
 401             return;
 402         }
 403         XToolkit.awtLock();
 404         try {
 405             XToolkit.removeEventDispatcher(window, xed);
 406         } finally {
 407             XToolkit.awtUnlock();
 408         }
 409     }
 410 
 411     // Private method for testing purposes.
 412     private Point getLocationOnScreen() {
 413         return eframe.getLocationOnScreen();
 414     }
 415 
 416     public Rectangle getBounds() {
 417         Point loc = getLocationOnScreen();
 418         return new Rectangle(loc.x, loc.y, loc.x + TRAY_ICON_WIDTH, loc.y + TRAY_ICON_HEIGHT);
 419     }
 420 
 421     void addListeners() {
 422         canvas.addMouseListener(eventProxy);
 423         canvas.addMouseMotionListener(eventProxy);
 424         eframe.addMouseListener(eventProxy);
 425     }
 426 
 427     void removeListeners() {
 428         canvas.removeMouseListener(eventProxy);
 429         canvas.removeMouseMotionListener(eventProxy);
 430         eframe.removeMouseListener(eventProxy);
 431     }
 432 
 433     long getWindow() {
 434         return AWTAccessor.getComponentAccessor()
 435                           .<XEmbeddedFramePeer>getPeer(eframe).getWindow();
 436     }
 437 
 438     public boolean isDisposed() {
 439         return isDisposed;
 440     }
 441 
 442     public String getActionCommand() {
 443         return target.getActionCommand();
 444     }
 445 
 446     static class TrayIconEventProxy implements MouseListener, MouseMotionListener {
 447         XTrayIconPeer xtiPeer;
 448 
 449         TrayIconEventProxy(XTrayIconPeer xtiPeer) {
 450             this.xtiPeer = xtiPeer;
 451         }
 452 
 453         public void handleEvent(MouseEvent e) {
 454             //prevent DRAG events from being posted with TrayIcon source(CR 6565779)
 455             if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
 456                 return;
 457             }
 458 
 459             // Event handling is synchronized with disposal by EDT.
 460             if (xtiPeer.isDisposed()) {
 461                 return;
 462             }
 463             Point coord = XBaseWindow.toOtherWindow(xtiPeer.getWindow(),
 464                                                     XToolkit.getDefaultRootWindow(),
 465                                                     e.getX(), e.getY());
 466 
 467             if (e.isPopupTrigger()) {
 468                 xtiPeer.showPopupMenu(coord.x, coord.y);
 469             }
 470 
 471             e.translatePoint(coord.x - e.getX(), coord.y - e.getY());
 472             // This is a hack in order to set non-Component source to MouseEvent
 473             // instance.
 474             // In some cases this could lead to unpredictable result (e.g. when
 475             // other class tries to cast source field to Component).
 476             // We already filter DRAG events out (CR 6565779).
 477             e.setSource(xtiPeer.target);
 478             XToolkit.postEvent(XToolkit.targetToAppContext(e.getSource()), e);
 479         }
 480         @SuppressWarnings("deprecation")
 481         public void mouseClicked(MouseEvent e) {
 482             if ((e.getClickCount() == 1 || xtiPeer.balloon.isVisible()) &&
 483                 e.getButton() == MouseEvent.BUTTON1)
 484             {
 485                 ActionEvent aev = new ActionEvent(xtiPeer.target, ActionEvent.ACTION_PERFORMED,
 486                                                   xtiPeer.target.getActionCommand(), e.getWhen(),
 487                                                   e.getModifiers());
 488                 XToolkit.postEvent(XToolkit.targetToAppContext(aev.getSource()), aev);
 489             }
 490             if (xtiPeer.balloon.isVisible()) {
 491                 xtiPeer.balloon.hide();
 492             }
 493             handleEvent(e);
 494         }
 495         public void mouseEntered(MouseEvent e) {
 496             xtiPeer.tooltip.enter();
 497             handleEvent(e);
 498         }
 499         public void mouseExited(MouseEvent e) {
 500             xtiPeer.tooltip.exit();
 501             handleEvent(e);
 502         }
 503         public void mousePressed(MouseEvent e) {
 504             handleEvent(e);
 505         }
 506         public void mouseReleased(MouseEvent e) {
 507             handleEvent(e);
 508         }
 509         public void mouseDragged(MouseEvent e) {
 510             handleEvent(e);
 511         }
 512         public void mouseMoved(MouseEvent e) {
 513             handleEvent(e);
 514         }
 515     }
 516 
 517     // ***************************************
 518     // Special embedded frame for tray icon
 519     // ***************************************
 520 
 521     @SuppressWarnings("serial") // JDK-implementation class
 522     private static class XTrayIconEmbeddedFrame extends XEmbeddedFrame {
 523         public XTrayIconEmbeddedFrame(){
 524             super(XToolkit.getDefaultRootWindow(), true, true);
 525         }
 526 
 527         public boolean isUndecorated() {
 528             return true;
 529         }
 530 
 531         public boolean isResizable() {
 532             return false;
 533         }
 534 
 535         // embedded frame for tray icon shouldn't be disposed by anyone except tray icon
 536         public void dispose(){
 537         }
 538 
 539         public void realDispose(){
 540             super.dispose();
 541         }
 542     };
 543 
 544     // ***************************************
 545     // Classes for painting an image on canvas
 546     // ***************************************
 547 
 548     @SuppressWarnings("serial") // JDK-implementation class
 549     static class TrayIconCanvas extends IconCanvas {
 550         TrayIcon target;
 551         boolean autosize;
 552 
 553         TrayIconCanvas(TrayIcon target, int width, int height) {
 554             super(width, height);
 555             this.target = target;
 556         }
 557 
 558         // Invoke on EDT.
 559         protected void repaintImage(boolean doClear) {
 560             boolean old_autosize = autosize;
 561             autosize = target.isImageAutoSize();
 562 
 563             curW = autosize ? width : image.getWidth(observer);
 564             curH = autosize ? height : image.getHeight(observer);
 565 
 566             super.repaintImage(doClear || (old_autosize != autosize));
 567         }
 568 
 569         public void dispose() {
 570             super.dispose();
 571             target = null;
 572         }
 573     }
 574 
 575     @SuppressWarnings("serial") // JDK-implementation class
 576     public static class IconCanvas extends Canvas {
 577         volatile Image image;
 578         IconObserver observer;
 579         int width, height;
 580         int curW, curH;
 581 
 582         IconCanvas(int width, int height) {
 583             this.width = curW = width;
 584             this.height = curH = height;
 585         }
 586 
 587         // Invoke on EDT.
 588         public void updateImage(Image image) {
 589             this.image = image;
 590             if (observer == null) {
 591                 observer = new IconObserver();
 592             }
 593             repaintImage(true);
 594         }
 595 
 596         public void dispose() {
 597             observer = null;
 598         }
 599 
 600         // Invoke on EDT.
 601         protected void repaintImage(boolean doClear) {
 602             Graphics g = getGraphics();
 603             if (g != null) {
 604                 try {
 605                     if (isVisible()) {
 606                         if (doClear) {
 607                             update(g);
 608                         } else {
 609                             paint(g);
 610                         }
 611                     }
 612                 } finally {
 613                     g.dispose();
 614                 }
 615             }
 616         }
 617 
 618         // Invoke on EDT.
 619         public void paint(Graphics g) {
 620             if (g != null && curW > 0 && curH > 0) {
 621                 BufferedImage bufImage = new BufferedImage(curW, curH, BufferedImage.TYPE_INT_ARGB);
 622                 Graphics2D gr = bufImage.createGraphics();
 623                 if (gr != null) {
 624                     try {
 625                         gr.setColor(getBackground());
 626                         gr.fillRect(0, 0, curW, curH);
 627                         gr.drawImage(image, 0, 0, curW, curH, observer);
 628                         gr.dispose();
 629 
 630                         g.drawImage(bufImage, 0, 0, curW, curH, null);
 631                     } finally {
 632                         gr.dispose();
 633                     }
 634                 }
 635             }
 636         }
 637 
 638         class IconObserver implements ImageObserver {
 639             public boolean imageUpdate(final Image image, final int flags, int x, int y, int width, int height) {
 640                 if (image != IconCanvas.this.image || // if the image has been changed
 641                     !IconCanvas.this.isVisible())
 642                 {
 643                     return false;
 644                 }
 645                 if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS |
 646                               ImageObserver.WIDTH | ImageObserver.HEIGHT)) != 0)
 647                 {
 648                     SunToolkit.executeOnEventHandlerThread(IconCanvas.this, new Runnable() {
 649                             public void run() {
 650                                 repaintImage(false);
 651                             }
 652                         });
 653                 }
 654                 return (flags & ImageObserver.ALLBITS) == 0;
 655             }
 656         }
 657     }
 658 }