JDK-8147841 : [macosx] Updating TrayIcons popup menu does not work on Mac OSX
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 8u60,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: os_x
  • CPU: x86
  • Submitted: 2016-01-18
  • Updated: 2017-01-10
  • Resolved: 2016-04-13
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9 b117Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Darwin Rainers-MBP-2.home 15.2.0 Darwin Kernel Version 15.2.0: Fri Nov 13 19:56:56 PST 2015; root:xnu-3248.20.55~2/RELEASE_X86_64 x86_64

A DESCRIPTION OF THE PROBLEM :
It seems to be impossible to update the popup menu of a TrayIcon, see attached sample program.
This issue might be related to JDK-8007220 but while the test program there works as expected, this current bug is still visible.

This bug does not seem to appear on Windows.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. create a TrayIcon with a popup menu
2. upon some event (e.g. MenuItem action) update the popup menu

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
the popup menu should be updated
ACTUAL -
the initially set popup menu is still visible

ERROR MESSAGES/STACK TRACES THAT OCCUR :
no error is shown

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import javax.swing.*;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;

public class TrayIconBug {
    /**
     * This class demostrates a bug in the TrayIcon class in which
     * updates to a TrayIcons PopupMenu added to a SystemTray do not take effect.
     * <p>
     * The issue might be related to JDK-8007220 but affects all JDK versions even if they pass the test with the sample
     * application.
     */

    private static final String bugId = "1";
    private static final String applicationName = "TrayIconBug" + bugId;

    private static int trayIconImageSize = 32;
    private static int trayIconImageInset = 4;

    /**
     * Create a small image of a green/red circle to use as the icon for the tray icon
     *
     * @param addSystemTrayBeforeMenuItemsAreAdded if true, a red cirle is returned. If false, a green circle is returned
     * @return the Image created
     */
    private static Image createTrayIconImage(final boolean addSystemTrayBeforeMenuItemsAreAdded) {
        /**
         * Create a small image of a red circle to use as the icon for the tray icon
         */
        final BufferedImage trayImage = new BufferedImage(trayIconImageSize, trayIconImageSize, BufferedImage.TYPE_INT_ARGB);
        final Graphics2D trayImageGraphics = (Graphics2D) trayImage.getGraphics();

        trayImageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        trayImageGraphics.setColor(new Color(255, 255, 255, 0));
        trayImageGraphics.fillRect(0, 0, trayImage.getWidth(), trayImage.getHeight());

        if (addSystemTrayBeforeMenuItemsAreAdded) {
            trayImageGraphics.setColor(Color.red);
        } else {
            trayImageGraphics.setColor(Color.green);
        }

        trayImageGraphics.fillOval
                (trayIconImageInset,
                        trayIconImageInset,
                        trayImage.getWidth() - 2 * trayIconImageInset,
                        trayImage.getHeight() - 2 * trayIconImageInset);

        trayImageGraphics.setColor(Color.darkGray);

        trayImageGraphics.drawOval
                (trayIconImageInset,
                        trayIconImageInset,
                        trayImage.getWidth() - 2 * trayIconImageInset,
                        trayImage.getHeight() - 2 * trayIconImageInset);

        return trayImage;
    }

    private static PopupMenu createTrayIconPopupMenu(TrayIcon trayIcon) {
        return createTrayIconPopupMenu(trayIcon, 2);
    }

    /**
     * Create popup menu for system tray. Creates a popup menu like:
     * <p>
     * Menu Item 1
     * Menu Item 2
     * Sub menu
     * Submenu Item 1
     * Submenu Item 2
     * <p>
     *     In addition, if a menu item (Menu Item 1 or 2) is clicked, it *should* update the menu to have 3 menu items
     *     (etc.).
     * </p>
     *
     * @return the PopupMenu created
     */
    private static PopupMenu createTrayIconPopupMenu(final TrayIcon trayIcon, final int menuCount) {
        final PopupMenu trayIconPopupMenu = new PopupMenu();

        for (int i = 1; i <= menuCount; i++) {
            final String popupMenuItemText = "Menu Item " + i;

            final MenuItem popupMenuItem = new MenuItem(popupMenuItemText);

            popupMenuItem.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent ae) {
                    System.out.println(popupMenuItemText + " action listener called, updating popup menu to have "
                            + (menuCount + 1) + " elements...");
                    trayIcon.setPopupMenu(createTrayIconPopupMenu(trayIcon, menuCount + 1));
                }
            });

            trayIconPopupMenu.add(popupMenuItem);
        }

        final PopupMenu popupSubMenu = new PopupMenu("Submenu");

        for (int i = 1; i <= 2; i++) {
            final String popupMenuItemText = "Submenu Item " + i;

            final MenuItem popupMenuItem = new MenuItem(popupMenuItemText);

            popupMenuItem.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent ae) {
                    System.out.println(popupMenuItemText + " action listener called...");
                }
            });

            popupSubMenu.add(popupMenuItem);
        }

        trayIconPopupMenu.add(popupSubMenu);

        return trayIconPopupMenu;
    }

    private static void createSystemTrayIcons() {
            /**
             * Create a tray icon with two submenus: "Menu item 1" & "Menu item 2". This tray icon
             * is visible in the status bar on Mac OS X as a green. Clicking Menu Item 1 or 2 will try to
             * set a new popup menu with 3 menu items, if everything works, clicking one of the 3 menu items will add
             * yet one more.
             */
            final TrayIcon trayIcon = new TrayIcon(createTrayIconImage(true));
            trayIcon.setImageAutoSize(true);
            trayIcon.setToolTip(applicationName + " system tray");

            try {
                // Add tray icon action listenr for logging clicks on the tray icon
                trayIcon.addActionListener(new ActionListener() {
                    public void actionPerformed(final ActionEvent ae) {
                        System.out.println("Tray icon action listener called...");
                    }
                });

                trayIcon.setPopupMenu(createTrayIconPopupMenu(trayIcon));

                // Add tray icon to system tray
                SystemTray.getSystemTray().add(trayIcon);
            } catch (final AWTException awte) {
                awte.printStackTrace();
            }
    }

    private static void createFrame() {
        /**
         * Create a small window on the screen to make the application visible
         * to the user. A small explanation is included in the window shown.
         *
         * Clicking the close button of the window quits the application
         */
        final JPanel panel = new JPanel();
        panel.setBorder(new CompoundBorder(panel.getBorder(), new EmptyBorder(15, 15, 15, 15)));
        panel.setLayout(new BorderLayout());

        panel.add(new JLabel(applicationName + " demo application"), BorderLayout.NORTH);
        panel.add(new JLabel("Click one of the top level menu items to set a new popup menu with 3 menu items."), BorderLayout.CENTER);

        final JFrame frame = new JFrame(applicationName + " information");
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        frame.add(panel);

        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(final WindowEvent we) {
                System.exit(0);
            }
        });

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(final String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                if (SystemTray.isSupported()) {
                    createSystemTrayIcons();

                    createFrame();
                }
            }
        });
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
there doesn't seem to be a workaround. If there is one I would be very interested.


Comments
Fix Explanation with code points: File CTrayIcon.m: On mouseDown event java method getPopupMenuModel was called using JNFCallLongMethod. getPopupMenuModel on the java side was always returning the address of the popup menu created initially and because of this updated popup menus was not getting shown on clicking the Tray icon. Fix Explanation: In method getPopupMenuModel the address of the new popup menu is updated in case there is a new popup menu. If the new popup points to null then 0 is returned as address. And in cases the new popup and the initial popup menu are same then the initial popup menu's address is returned. Scenarios taken care in the fix: Case 1) popup and newPopup refers to same object. In cases where the same popup menu is used thorughout the execution of the program or both of them are null. a) first popup menu is null return 0 as address. b) first popup menu is not null poupup is already set in the constructor. Case 2) popup and newPopup refers to different objects. a) newPopup is not null. i) popup is not null call removeNotify() on popup and set it to newPopup ii) popup is null set popup to newPoup b) newPopup is null then return 0 address (In this case the first popup menu is not checked for null because we are interested in the newPopup). Webrev link: http://cr.openjdk.java.net/~aghaisas/manajit/8147841/webrev.01/
14-04-2016

Wrong address of Popup menu was referred by native code (Mac OS). The native side was always referring to the popup menu created initially and the popup menu created later was not referred by the native code. Popup menu was getting updated internally (in java code), the new popup menu was not getting accessed/used by the native code (mac os x).
30-03-2016

After debugging it was found that after adding tray icon (after a call to SystemTray.getSystemTray().add(trayIcon) the PopupMenu can not be changed. Updating TrayIcons popup menu will work if the same PopupMenu is used. In the reported problems code the PopupMenu is changed every time before updating the TrayIcons popup menu. But the TrayIcon is using the first PopupMenu created before the call the SystemTray.getSystemTray().add(trayIcon). The duplicate issue (https://bugs.openjdk.java.net/browse/JDK-8007220) is resolved and claims to solve this issue. But the fix in the issue JDK-8007220 also fails in same way (update tray icon PopupMenu fails).
04-02-2016

P3 - ILW (M, M, M)
21-01-2016

The issue is reproduced on MacOS 10.10.5 with JDK 9.1. The initially set popup menu is still visible after clicking on the menu items.
21-01-2016