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