1 /*
2  * Copyright (c) 2005, 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 java.awt;
27 
28 import java.util.Vector;
29 import java.awt.peer.SystemTrayPeer;
30 import java.beans.PropertyChangeListener;
31 import java.beans.PropertyChangeSupport;
32 import sun.awt.AppContext;
33 import sun.awt.SunToolkit;
34 import sun.awt.HeadlessToolkit;
35 import sun.security.util.SecurityConstants;
36 import sun.awt.AWTAccessor;
37 
38 /**
39  * The <code>SystemTray</code> class represents the system tray for a
40  * desktop.  On Microsoft Windows it is referred to as the "Taskbar
41  * Status Area", on Gnome it is referred to as the "Notification
42  * Area", on KDE it is referred to as the "System Tray".  The system
43  * tray is shared by all applications running on the desktop.
44  *
45  * <p> On some platforms the system tray may not be present or may not
46  * be supported, in this case {@link SystemTray#getSystemTray()}
47  * throws {@link UnsupportedOperationException}.  To detect whether the
48  * system tray is supported, use {@link SystemTray#isSupported}.
49  *
50  * <p>The <code>SystemTray</code> may contain one or more {@link
51  * TrayIcon TrayIcons}, which are added to the tray using the {@link
52  * #add} method, and removed when no longer needed, using the
53  * {@link #remove}.  <code>TrayIcon</code> consists of an
54  * image, a popup menu and a set of associated listeners.  Please see
55  * the {@link TrayIcon} class for details.
56  *
57  * <p>Every Java application has a single <code>SystemTray</code>
58  * instance that allows the app to interface with the system tray of
59  * the desktop while the app is running.  The <code>SystemTray</code>
60  * instance can be obtained from the {@link #getSystemTray} method.
61  * An application may not create its own instance of
62  * <code>SystemTray</code>.
63  *
64  * <p>The following code snippet demonstrates how to access
65  * and customize the system tray:
66  * <pre>
67  * <code>
68  *     {@link TrayIcon} trayIcon = null;
69  *     if (SystemTray.isSupported()) {
70  *         // get the SystemTray instance
71  *         SystemTray tray = SystemTray.{@link #getSystemTray};
72  *         // load an image
73  *         {@link java.awt.Image} image = {@link java.awt.Toolkit#getImage(String) Toolkit.getDefaultToolkit().getImage}(...);
74  *         // create a action listener to listen for default action executed on the tray icon
75  *         {@link java.awt.event.ActionListener} listener = new {@link java.awt.event.ActionListener ActionListener}() {
76  *             public void {@link java.awt.event.ActionListener#actionPerformed actionPerformed}({@link java.awt.event.ActionEvent} e) {
77  *                 // execute default action of the application
78  *                 // ...
79  *             }
80  *         };
81  *         // create a popup menu
82  *         {@link java.awt.PopupMenu} popup = new {@link java.awt.PopupMenu#PopupMenu PopupMenu}();
83  *         // create menu item for the default action
84  *         MenuItem defaultItem = new MenuItem(...);
85  *         defaultItem.addActionListener(listener);
86  *         popup.add(defaultItem);
87  *         /// ... add other items
88  *         // construct a TrayIcon
89  *         trayIcon = new {@link TrayIcon#TrayIcon(java.awt.Image, String, java.awt.PopupMenu) TrayIcon}(image, "Tray Demo", popup);
90  *         // set the TrayIcon properties
91  *         trayIcon.{@link TrayIcon#addActionListener(java.awt.event.ActionListener) addActionListener}(listener);
92  *         // ...
93  *         // add the tray image
94  *         try {
95  *             tray.{@link SystemTray#add(TrayIcon) add}(trayIcon);
96  *         } catch (AWTException e) {
97  *             System.err.println(e);
98  *         }
99  *         // ...
100  *     } else {
101  *         // disable tray option in your application or
102  *         // perform other actions
103  *         ...
104  *     }
105  *     // ...
106  *     // some time later
107  *     // the application state has changed - update the image
108  *     if (trayIcon != null) {
109  *         trayIcon.{@link TrayIcon#setImage(java.awt.Image) setImage}(updatedImage);
110  *     }
111  *     // ...
112  * </code>
113  * </pre>
114  *
115  * @since 1.6
116  * @see TrayIcon
117  *
118  * @author Bino George
119  * @author Denis Mikhalkin
120  * @author Sharon Zakhour
121  * @author Anton Tarasov
122  */
123 public class SystemTray {
124     private static SystemTray systemTray;
125     private int currentIconID = 0; // each TrayIcon added gets a unique ID
126 
127     transient private SystemTrayPeer peer;
128 
129     private static final TrayIcon[] EMPTY_TRAY_ARRAY = new TrayIcon[0];
130 
131     static {
AWTAccessor.setSystemTrayAccessor( new AWTAccessor.SystemTrayAccessor() { public void firePropertyChange(SystemTray tray, String propertyName, Object oldValue, Object newValue) { tray.firePropertyChange(propertyName, oldValue, newValue); } })132         AWTAccessor.setSystemTrayAccessor(
133             new AWTAccessor.SystemTrayAccessor() {
134                 public void firePropertyChange(SystemTray tray,
135                                                String propertyName,
136                                                Object oldValue,
137                                                Object newValue) {
138                     tray.firePropertyChange(propertyName, oldValue, newValue);
139                 }
140             });
141     }
142 
143     /**
144      * Private <code>SystemTray</code> constructor.
145      *
146      */
SystemTray()147     private SystemTray() {
148         addNotify();
149     }
150 
151     /**
152      * Gets the <code>SystemTray</code> instance that represents the
153      * desktop's tray area.  This always returns the same instance per
154      * application.  On some platforms the system tray may not be
155      * supported.  You may use the {@link #isSupported} method to
156      * check if the system tray is supported.
157      *
158      * <p>If a SecurityManager is installed, the AWTPermission
159      * {@code accessSystemTray} must be granted in order to get the
160      * {@code SystemTray} instance. Otherwise this method will throw a
161      * SecurityException.
162      *
163      * @return the <code>SystemTray</code> instance that represents
164      * the desktop's tray area
165      * @throws UnsupportedOperationException if the system tray isn't
166      * supported by the current platform
167      * @throws HeadlessException if
168      * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>
169      * @throws SecurityException if {@code accessSystemTray} permission
170      * is not granted
171      * @see #add(TrayIcon)
172      * @see TrayIcon
173      * @see #isSupported
174      * @see SecurityManager#checkPermission
175      * @see AWTPermission
176      */
getSystemTray()177     public static SystemTray getSystemTray() {
178         checkSystemTrayAllowed();
179         if (GraphicsEnvironment.isHeadless()) {
180             throw new HeadlessException();
181         }
182 
183         initializeSystemTrayIfNeeded();
184 
185         if (!isSupported()) {
186             throw new UnsupportedOperationException(
187                 "The system tray is not supported on the current platform.");
188         }
189 
190         return systemTray;
191     }
192 
193     /**
194      * Returns whether the system tray is supported on the current
195      * platform.  In addition to displaying the tray icon, minimal
196      * system tray support includes either a popup menu (see {@link
197      * TrayIcon#setPopupMenu(PopupMenu)}) or an action event (see
198      * {@link TrayIcon#addActionListener(ActionListener)}).
199      *
200      * <p>Developers should not assume that all of the system tray
201      * functionality is supported.  To guarantee that the tray icon's
202      * default action is always accessible, add the default action to
203      * both the action listener and the popup menu.  See the {@link
204      * SystemTray example} for an example of how to do this.
205      *
206      * <p><b>Note</b>: When implementing <code>SystemTray</code> and
207      * <code>TrayIcon</code> it is <em>strongly recommended</em> that
208      * you assign different gestures to the popup menu and an action
209      * event.  Overloading a gesture for both purposes is confusing
210      * and may prevent the user from accessing one or the other.
211      *
212      * @see #getSystemTray
213      * @return <code>false</code> if no system tray access is supported; this
214      * method returns <code>true</code> if the minimal system tray access is
215      * supported but does not guarantee that all system tray
216      * functionality is supported for the current platform
217      */
isSupported()218     public static boolean isSupported() {
219         Toolkit toolkit = Toolkit.getDefaultToolkit();
220         if (toolkit instanceof SunToolkit) {
221             // connecting tray to native resource
222             initializeSystemTrayIfNeeded();
223             return ((SunToolkit)toolkit).isTraySupported();
224         } else if (toolkit instanceof HeadlessToolkit) {
225             // skip initialization as the init routine
226             // throws HeadlessException
227             return ((HeadlessToolkit)toolkit).isTraySupported();
228         } else {
229             return false;
230         }
231     }
232 
233     /**
234      * Adds a <code>TrayIcon</code> to the <code>SystemTray</code>.
235      * The tray icon becomes visible in the system tray once it is
236      * added.  The order in which icons are displayed in a tray is not
237      * specified - it is platform and implementation-dependent.
238      *
239      * <p> All icons added by the application are automatically
240      * removed from the <code>SystemTray</code> upon application exit
241      * and also when the desktop system tray becomes unavailable.
242      *
243      * @param trayIcon the <code>TrayIcon</code> to be added
244      * @throws NullPointerException if <code>trayIcon</code> is
245      * <code>null</code>
246      * @throws IllegalArgumentException if the same instance of
247      * a <code>TrayIcon</code> is added more than once
248      * @throws AWTException if the desktop system tray is missing
249      * @see #remove(TrayIcon)
250      * @see #getSystemTray
251      * @see TrayIcon
252      * @see java.awt.Image
253      */
add(TrayIcon trayIcon)254     public void add(TrayIcon trayIcon) throws AWTException {
255         if (trayIcon == null) {
256             throw new NullPointerException("adding null TrayIcon");
257         }
258         TrayIcon[] oldArray = null, newArray = null;
259         Vector<TrayIcon> icons = null;
260         synchronized (this) {
261             oldArray = systemTray.getTrayIcons();
262             icons = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class);
263             if (icons == null) {
264                 icons = new Vector<TrayIcon>(3);
265                 AppContext.getAppContext().put(TrayIcon.class, icons);
266 
267             } else if (icons.contains(trayIcon)) {
268                 throw new IllegalArgumentException("adding TrayIcon that is already added");
269             }
270             icons.add(trayIcon);
271             newArray = systemTray.getTrayIcons();
272 
273             trayIcon.setID(++currentIconID);
274         }
275         try {
276             trayIcon.addNotify();
277         } catch (AWTException e) {
278             icons.remove(trayIcon);
279             throw e;
280         }
281         firePropertyChange("trayIcons", oldArray, newArray);
282     }
283 
284     /**
285      * Removes the specified <code>TrayIcon</code> from the
286      * <code>SystemTray</code>.
287      *
288      * <p> All icons added by the application are automatically
289      * removed from the <code>SystemTray</code> upon application exit
290      * and also when the desktop system tray becomes unavailable.
291      *
292      * <p> If <code>trayIcon</code> is <code>null</code> or was not
293      * added to the system tray, no exception is thrown and no action
294      * is performed.
295      *
296      * @param trayIcon the <code>TrayIcon</code> to be removed
297      * @see #add(TrayIcon)
298      * @see TrayIcon
299      */
remove(TrayIcon trayIcon)300     public void remove(TrayIcon trayIcon) {
301         if (trayIcon == null) {
302             return;
303         }
304         TrayIcon[] oldArray = null, newArray = null;
305         synchronized (this) {
306             oldArray = systemTray.getTrayIcons();
307             Vector<TrayIcon> icons = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class);
308             // TrayIcon with no peer is not contained in the array.
309             if (icons == null || !icons.remove(trayIcon)) {
310                 return;
311             }
312             trayIcon.removeNotify();
313             newArray = systemTray.getTrayIcons();
314         }
315         firePropertyChange("trayIcons", oldArray, newArray);
316     }
317 
318     /**
319      * Returns an array of all icons added to the tray by this
320      * application.  You can't access the icons added by another
321      * application.  Some browsers partition applets in different
322      * code bases into separate contexts, and establish walls between
323      * these contexts.  In such a scenario, only the tray icons added
324      * from this context will be returned.
325      *
326      * <p> The returned array is a copy of the actual array and may be
327      * modified in any way without affecting the system tray.  To
328      * remove a <code>TrayIcon</code> from the
329      * <code>SystemTray</code>, use the {@link
330      * #remove(TrayIcon)} method.
331      *
332      * @return an array of all tray icons added to this tray, or an
333      * empty array if none has been added
334      * @see #add(TrayIcon)
335      * @see TrayIcon
336      */
getTrayIcons()337     public TrayIcon[] getTrayIcons() {
338         Vector<TrayIcon> icons = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class);
339         if (icons != null) {
340             return (TrayIcon[])icons.toArray(new TrayIcon[icons.size()]);
341         }
342         return EMPTY_TRAY_ARRAY;
343     }
344 
345     /**
346      * Returns the size, in pixels, of the space that a tray icon will
347      * occupy in the system tray.  Developers may use this methods to
348      * acquire the preferred size for the image property of a tray icon
349      * before it is created.  For convenience, there is a similar
350      * method {@link TrayIcon#getSize} in the <code>TrayIcon</code> class.
351      *
352      * @return the default size of a tray icon, in pixels
353      * @see TrayIcon#setImageAutoSize(boolean)
354      * @see java.awt.Image
355      * @see TrayIcon#getSize()
356      */
getTrayIconSize()357     public Dimension getTrayIconSize() {
358         return peer.getTrayIconSize();
359     }
360 
361     /**
362      * Adds a {@code PropertyChangeListener} to the list of listeners for the
363      * specific property. The following properties are currently supported:
364      *
365      * <table border=1 summary="SystemTray properties">
366      * <tr>
367      *    <th>Property</th>
368      *    <th>Description</th>
369      * </tr>
370      * <tr>
371      *    <td>{@code trayIcons}</td>
372      *    <td>The {@code SystemTray}'s array of {@code TrayIcon} objects.
373      *        The array is accessed via the {@link #getTrayIcons} method.<br>
374      *        This property is changed when a tray icon is added to (or removed
375      *        from) the system tray.<br> For example, this property is changed
376      *        when the system tray becomes unavailable on the desktop<br>
377      *        and the tray icons are automatically removed.</td>
378      * </tr>
379      * <tr>
380      *    <td>{@code systemTray}</td>
381      *    <td>This property contains {@code SystemTray} instance when the system tray
382      *        is available or <code>null</code> otherwise.<br> This property is changed
383      *        when the system tray becomes available or unavailable on the desktop.<br>
384      *        The property is accessed by the {@link #getSystemTray} method.</td>
385      * </tr>
386      * </table>
387      * <p>
388      * The {@code listener} listens to property changes only in this context.
389      * <p>
390      * If {@code listener} is {@code null}, no exception is thrown
391      * and no action is performed.
392      *
393      * @param propertyName the specified property
394      * @param listener the property change listener to be added
395      *
396      * @see #removePropertyChangeListener
397      * @see #getPropertyChangeListeners
398      */
addPropertyChangeListener(String propertyName, PropertyChangeListener listener)399     public synchronized void addPropertyChangeListener(String propertyName,
400                                                        PropertyChangeListener listener)
401     {
402         if (listener == null) {
403             return;
404         }
405         getCurrentChangeSupport().addPropertyChangeListener(propertyName, listener);
406     }
407 
408     /**
409      * Removes a {@code PropertyChangeListener} from the listener list
410      * for a specific property.
411      * <p>
412      * The {@code PropertyChangeListener} must be from this context.
413      * <p>
414      * If {@code propertyName} or {@code listener} is {@code null} or invalid,
415      * no exception is thrown and no action is taken.
416      *
417      * @param propertyName the specified property
418      * @param listener the PropertyChangeListener to be removed
419      *
420      * @see #addPropertyChangeListener
421      * @see #getPropertyChangeListeners
422      */
removePropertyChangeListener(String propertyName, PropertyChangeListener listener)423     public synchronized void removePropertyChangeListener(String propertyName,
424                                                           PropertyChangeListener listener)
425     {
426         if (listener == null) {
427             return;
428         }
429         getCurrentChangeSupport().removePropertyChangeListener(propertyName, listener);
430     }
431 
432     /**
433      * Returns an array of all the listeners that have been associated
434      * with the named property.
435      * <p>
436      * Only the listeners in this context are returned.
437      *
438      * @param propertyName the specified property
439      * @return all of the {@code PropertyChangeListener}s associated with
440      *         the named property; if no such listeners have been added or
441      *         if {@code propertyName} is {@code null} or invalid, an empty
442      *         array is returned
443      *
444      * @see #addPropertyChangeListener
445      * @see #removePropertyChangeListener
446      */
getPropertyChangeListeners(String propertyName)447     public synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
448         return getCurrentChangeSupport().getPropertyChangeListeners(propertyName);
449     }
450 
451 
452     // ***************************************************************
453     // ***************************************************************
454 
455 
456     /**
457      * Support for reporting bound property changes for Object properties.
458      * This method can be called when a bound property has changed and it will
459      * send the appropriate PropertyChangeEvent to any registered
460      * PropertyChangeListeners.
461      *
462      * @param propertyName the property whose value has changed
463      * @param oldValue the property's previous value
464      * @param newValue the property's new value
465      */
firePropertyChange(String propertyName, Object oldValue, Object newValue)466     private void firePropertyChange(String propertyName,
467                                     Object oldValue, Object newValue)
468     {
469         if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
470             return;
471         }
472         getCurrentChangeSupport().firePropertyChange(propertyName, oldValue, newValue);
473     }
474 
475     /**
476      * Returns the current PropertyChangeSupport instance for the
477      * calling thread's context.
478      *
479      * @return this thread's context's PropertyChangeSupport
480      */
getCurrentChangeSupport()481     private synchronized PropertyChangeSupport getCurrentChangeSupport() {
482         PropertyChangeSupport changeSupport =
483             (PropertyChangeSupport)AppContext.getAppContext().get(SystemTray.class);
484 
485         if (changeSupport == null) {
486             changeSupport = new PropertyChangeSupport(this);
487             AppContext.getAppContext().put(SystemTray.class, changeSupport);
488         }
489         return changeSupport;
490     }
491 
addNotify()492     synchronized void addNotify() {
493         if (peer == null) {
494             Toolkit toolkit = Toolkit.getDefaultToolkit();
495             if (toolkit instanceof SunToolkit) {
496                 peer = ((SunToolkit)Toolkit.getDefaultToolkit()).createSystemTray(this);
497             } else if (toolkit instanceof HeadlessToolkit) {
498                 peer = ((HeadlessToolkit)Toolkit.getDefaultToolkit()).createSystemTray(this);
499             }
500         }
501     }
502 
checkSystemTrayAllowed()503     static void checkSystemTrayAllowed() {
504         SecurityManager security = System.getSecurityManager();
505         if (security != null) {
506             security.checkPermission(SecurityConstants.AWT.ACCESS_SYSTEM_TRAY_PERMISSION);
507         }
508     }
509 
initializeSystemTrayIfNeeded()510     private static void initializeSystemTrayIfNeeded() {
511         synchronized (SystemTray.class) {
512             if (systemTray == null) {
513                 systemTray = new SystemTray();
514             }
515         }
516     }
517 }
518