View Javadoc

1   package uk.ac.roe.antigen.utils;
2   
3   import java.io.File;
4   import java.io.FileNotFoundException;
5   import java.io.IOException;
6   import java.lang.reflect.Constructor;
7   import java.lang.reflect.Field;
8   import java.lang.reflect.InvocationTargetException;
9   import java.lang.reflect.Method;
10  
11  /***
12   * BrowserLauncher is a class that provides one static method, openURL, which opens the default
13   * web browser for the current user of the system to the given URL.  It may support other
14   * protocols depending on the system -- mailto, ftp, etc. -- but that has not been rigorously
15   * tested and is not guaranteed to work.
16   * <p>
17   * Yes, this is platform-specific code, and yes, it may rely on classes on certain platforms
18   * that are not part of the standard JDK.  What we're trying to do, though, is to take something
19   * that's frequently desirable but inherently platform-specific -- opening a default browser --
20   * and allow programmers (you, for example) to do so without worrying about dropping into native
21   * code or doing anything else similarly evil.
22   * <p>
23   * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant systems without
24   * modification or a need for additional libraries.  All classes that are required on certain
25   * platforms to allow this to run are dynamically loaded at runtime via reflection and, if not
26   * found, will not cause this to do anything other than returning an error when opening the
27   * browser.
28   * <p>
29   * There are certain system requirements for this class, as it's running through Runtime.exec(),
30   * which is Java's way of making a native system call.  Currently, this requires that a Macintosh
31   * have a Finder which supports the GURL event, which is true for Mac OS 8.0 and 8.1 systems that
32   * have the Internet Scripting AppleScript dictionary installed in the Scripting Additions folder
33   * in the Extensions folder (which is installed by default as far as I know under Mac OS 8.0 and
34   * 8.1), and for all Mac OS 8.5 and later systems.  On Windows, it only runs under Win32 systems
35   * (Windows 95, 98, and NT 4.0, as well as later versions of all).  On other systems, this drops
36   * back from the inherently platform-sensitive concept of a default browser and simply attempts
37   * to launch Netscape via a shell command.
38   * <p>
39   * This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu) and may be
40   * redistributed or modified in any form without restrictions as long as the portion of this
41   * comment from this paragraph through the end of the comment is not removed.  The author
42   * requests that he be notified of any application, applet, or other binary that makes use of
43   * this code, but that's more out of curiosity than anything and is not required.  This software
44   * includes no warranty.  The author is not repsonsible for any loss of data or functionality
45   * or any adverse or unexpected effects of using this software.
46   * <p>
47   * Credits:
48   * <br>Steven Spencer, JavaWorld magazine (<a href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java Tip 66</a>)
49   * <br>Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea Cantatore,
50   * Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk
51   *
52   * @author Eric Albert (<a href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu</a>)
53   * @version 1.4b1 (Released June 20, 2001)
54   */
55  public class BrowserLauncher {
56  
57      /***
58       * The Java virtual machine that we are running on.  Actually, in most cases we only care
59       * about the operating system, but some operating systems require us to switch on the VM. */
60      private static int jvm;
61  
62      /*** The browser for the system */
63      private static Object browser;
64  
65      /***
66       * Caches whether any classes, methods, and fields that are not part of the JDK and need to
67       * be dynamically loaded at runtime loaded successfully.
68       * <p>
69       * Note that if this is <code>false</code>, <code>openURL()</code> will always return an
70       * IOException.
71       */
72      private static boolean loadedWithoutErrors;
73  
74      /*** The com.apple.mrj.MRJFileUtils class */
75      private static Class mrjFileUtilsClass;
76  
77      /*** The com.apple.mrj.MRJOSType class */
78      private static Class mrjOSTypeClass;
79  
80      /*** The com.apple.MacOS.AEDesc class */
81      private static Class aeDescClass;
82      
83      /*** The <init>(int) method of com.apple.MacOS.AETarget */
84      private static Constructor aeTargetConstructor;
85      
86      /*** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */
87      private static Constructor appleEventConstructor;
88      
89      /*** The <init>(String) method of com.apple.MacOS.AEDesc */
90      private static Constructor aeDescConstructor;
91      
92      /*** The findFolder method of com.apple.mrj.MRJFileUtils */
93      private static Method findFolder;
94  
95      /*** The getFileCreator method of com.apple.mrj.MRJFileUtils */
96      private static Method getFileCreator;
97      
98      /*** The getFileType method of com.apple.mrj.MRJFileUtils */
99      private static Method getFileType;
100     
101     /*** The openURL method of com.apple.mrj.MRJFileUtils */
102     private static Method openURL;
103     
104     /*** The makeOSType method of com.apple.MacOS.OSUtils */
105     private static Method makeOSType;
106     
107     /*** The putParameter method of com.apple.MacOS.AppleEvent */
108     private static Method putParameter;
109     
110     /*** The sendNoReply method of com.apple.MacOS.AppleEvent */
111     private static Method sendNoReply;
112     
113     /*** Actually an MRJOSType pointing to the System Folder on a Macintosh */
114     private static Object kSystemFolderType;
115 
116     /*** The keyDirectObject AppleEvent parameter type */
117     private static Integer keyDirectObject;
118 
119     /*** The kAutoGenerateReturnID AppleEvent code */
120     private static Integer kAutoGenerateReturnID;
121     
122     /*** The kAnyTransactionID AppleEvent code */
123     private static Integer kAnyTransactionID;
124 
125     /*** The linkage object required for JDirect 3 on Mac OS X. */
126     private static Object linkage;
127     
128     /*** The framework to reference on Mac OS X */
129     private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox";
130 
131     /*** JVM constant for MRJ 2.0 */
132     private static final int MRJ_2_0 = 0;
133     
134     /*** JVM constant for MRJ 2.1 or later */
135     private static final int MRJ_2_1 = 1;
136 
137     /*** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */
138     private static final int MRJ_3_0 = 3;
139     
140     /*** JVM constant for MRJ 3.1 */
141     private static final int MRJ_3_1 = 4;
142 
143     /*** JVM constant for any Windows NT JVM */
144     private static final int WINDOWS_NT = 5;
145     
146     /*** JVM constant for any Windows 9x JVM */
147     private static final int WINDOWS_9x = 6;
148 
149     /*** JVM constant for any other platform */
150     private static final int OTHER = -1;
151 
152     /***
153      * The file type of the Finder on a Macintosh.  Hardcoding "Finder" would keep non-U.S. English
154      * systems from working properly.
155      */
156     private static final String FINDER_TYPE = "FNDR";
157 
158     /***
159      * The creator code of the Finder on a Macintosh, which is needed to send AppleEvents to the
160      * application.
161      */
162     private static final String FINDER_CREATOR = "MACS";
163 
164     /*** The name for the AppleEvent type corresponding to a GetURL event. */
165     private static final String GURL_EVENT = "GURL";
166 
167     /***
168      * The first parameter that needs to be passed into Runtime.exec() to open the default web
169      * browser on Windows.
170      */
171     private static final String FIRST_WINDOWS_PARAMETER = "/c";
172     
173     /*** The second parameter for Runtime.exec() on Windows. */
174     private static final String SECOND_WINDOWS_PARAMETER = "start";
175     
176     /***
177      * The third parameter for Runtime.exec() on Windows.  This is a "title"
178      * parameter that the command line expects.  Setting this parameter allows
179      * URLs containing spaces to work.
180      */
181     private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
182     
183     /***
184      * The shell parameters for Netscape that opens a given URL in an already-open copy of Netscape
185      * on many command-line systems.
186      */
187     private static final String NETSCAPE_REMOTE_PARAMETER = "-remote";
188     private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL(";
189     private static final String NETSCAPE_OPEN_PARAMETER_END = ")'";
190     
191     /***
192      * The message from any exception thrown throughout the initialization process.
193      */
194     private static String errorMessage;
195 
196     /***
197      * An initialization block that determines the operating system and loads the necessary
198      * runtime data.
199      */
200     static {
201         loadedWithoutErrors = true;
202         String osName = System.getProperty("os.name");
203         if (osName.startsWith("Mac OS")) {
204             String mrjVersion = System.getProperty("mrj.version");
205             String majorMRJVersion = mrjVersion.substring(0, 3);
206             try {
207                 double version = Double.valueOf(majorMRJVersion).doubleValue();
208                 if (version == 2) {
209                     jvm = MRJ_2_0;
210                 } else if (version >= 2.1 && version < 3) {
211                     // Assume that all 2.x versions of MRJ work the same.  MRJ 2.1 actually
212                     // works via Runtime.exec() and 2.2 supports that but has an openURL() method
213                     // as well that we currently ignore.
214                     jvm = MRJ_2_1;
215                 } else if (version == 3.0) {
216                     jvm = MRJ_3_0;
217                 } else if (version >= 3.1) {
218                     // Assume that all 3.1 and later versions of MRJ work the same.
219                     jvm = MRJ_3_1;
220                 } else {
221                     loadedWithoutErrors = false;
222                     errorMessage = "Unsupported MRJ version: " + version;
223                 }
224             } catch (NumberFormatException nfe) {
225                 loadedWithoutErrors = false;
226                 errorMessage = "Invalid MRJ version: " + mrjVersion;
227             }
228         } else if (osName.startsWith("Windows")) {
229             if (osName.indexOf("9") != -1) {
230                 jvm = WINDOWS_9x;
231             } else {
232                 jvm = WINDOWS_NT;
233             }
234         } else {
235             jvm = OTHER;
236         }
237         
238         if (loadedWithoutErrors) {  // if we haven't hit any errors yet
239             loadedWithoutErrors = loadClasses();
240         }
241     }
242 
243     /***
244      * This class should be never be instantiated; this just ensures so.
245      */
246     private BrowserLauncher() { }
247     
248     /***
249      * Called by a static initializer to load any classes, fields, and methods required at runtime
250      * to locate the user's web browser.
251      * @return <code>true</code> if all intialization succeeded
252      *          <code>false</code> if any portion of the initialization failed
253      */
254     private static boolean loadClasses() {
255         switch (jvm) {
256             case MRJ_2_0:
257                 try {
258                     Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
259                     Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
260                     Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent");
261                     Class aeClass = Class.forName("com.apple.MacOS.ae");
262                     aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
263 
264                     aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class [] { int.class });
265                     appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class, aeTargetClass, int.class, int.class });
266                     aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class });
267 
268                     makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class [] { String.class });
269                     putParameter = appleEventClass.getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass });
270                     sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] { });
271 
272                     Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject");
273                     keyDirectObject = (Integer) keyDirectObjectField.get(null);
274                     Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID");
275                     kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null);
276                     Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID");
277                     kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
278                 } catch (ClassNotFoundException cnfe) {
279                     errorMessage = cnfe.getMessage();
280                     return false;
281                 } catch (NoSuchMethodException nsme) {
282                     errorMessage = nsme.getMessage();
283                     return false;
284                 } catch (NoSuchFieldException nsfe) {
285                     errorMessage = nsfe.getMessage();
286                     return false;
287                 } catch (IllegalAccessException iae) {
288                     errorMessage = iae.getMessage();
289                     return false;
290                 }
291                 break;
292             case MRJ_2_1:
293                 try {
294                     mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
295                     mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
296                     Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType");
297                     kSystemFolderType = systemFolderField.get(null);
298                     findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass });
299                     getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class });
300                     getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class });
301                 } catch (ClassNotFoundException cnfe) {
302                     errorMessage = cnfe.getMessage();
303                     return false;
304                 } catch (NoSuchFieldException nsfe) {
305                     errorMessage = nsfe.getMessage();
306                     return false;
307                 } catch (NoSuchMethodException nsme) {
308                     errorMessage = nsme.getMessage();
309                     return false;
310                 } catch (SecurityException se) {
311                     errorMessage = se.getMessage();
312                     return false;
313                 } catch (IllegalAccessException iae) {
314                     errorMessage = iae.getMessage();
315                     return false;
316                 }
317                 break;
318             case MRJ_3_0:
319                 try {
320                     Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
321                     Constructor constructor = linker.getConstructor(new Class[]{ Class.class });
322                     linkage = constructor.newInstance(new Object[] { BrowserLauncher.class });
323                 } catch (ClassNotFoundException cnfe) {
324                     errorMessage = cnfe.getMessage();
325                     return false;
326                 } catch (NoSuchMethodException nsme) {
327                     errorMessage = nsme.getMessage();
328                     return false;
329                 } catch (InvocationTargetException ite) {
330                     errorMessage = ite.getMessage();
331                     return false;
332                 } catch (InstantiationException ie) {
333                     errorMessage = ie.getMessage();
334                     return false;
335                 } catch (IllegalAccessException iae) {
336                     errorMessage = iae.getMessage();
337                     return false;
338                 }
339                 break;
340             case MRJ_3_1:
341                 try {
342                     mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
343                     openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class });
344                 } catch (ClassNotFoundException cnfe) {
345                     errorMessage = cnfe.getMessage();
346                     return false;
347                 } catch (NoSuchMethodException nsme) {
348                     errorMessage = nsme.getMessage();
349                     return false;
350                 }
351                 break;
352             default:
353                 break;
354         }
355         return true;
356     }
357 
358     /***
359      * Attempts to locate the default web browser on the local system.  Caches results so it
360      * only locates the browser once for each use of this class per JVM instance.
361      * @return The browser for the system.  Note that this may not be what you would consider
362      *          to be a standard web browser; instead, it's the application that gets called to
363      *          open the default web browser.  In some cases, this will be a non-String object
364      *          that provides the means of calling the default browser.
365      */
366     private static Object locateBrowser() {
367         if (browser != null) {
368             return browser;
369         }
370         switch (jvm) {
371             case MRJ_2_0:
372                 try {
373                     Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR });
374                     Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode });
375                     Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT });
376                     Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID });
377                     // Don't set browser = appleEvent because then the next time we call
378                     // locateBrowser(), we'll get the same AppleEvent, to which we'll already have
379                     // added the relevant parameter. Instead, regenerate the AppleEvent every time.
380                     // There's probably a way to do this better; if any has any ideas, please let
381                     // me know.
382                     return appleEvent;
383                 } catch (IllegalAccessException iae) {
384                     browser = null;
385                     errorMessage = iae.getMessage();
386                     return browser;
387                 } catch (InstantiationException ie) {
388                     browser = null;
389                     errorMessage = ie.getMessage();
390                     return browser;
391                 } catch (InvocationTargetException ite) {
392                     browser = null;
393                     errorMessage = ite.getMessage();
394                     return browser;
395                 }
396             case MRJ_2_1:
397                 File systemFolder;
398                 try {
399                     systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType });
400                 } catch (IllegalArgumentException iare) {
401                     browser = null;
402                     errorMessage = iare.getMessage();
403                     return browser;
404                 } catch (IllegalAccessException iae) {
405                     browser = null;
406                     errorMessage = iae.getMessage();
407                     return browser;
408                 } catch (InvocationTargetException ite) {
409                     browser = null;
410                     errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
411                     return browser;
412                 }
413                 String[] systemFolderFiles = systemFolder.list();
414                 // Avoid a FilenameFilter because that can't be stopped mid-list
415                 for(int i = 0; i < systemFolderFiles.length; i++) {
416                     try {
417                         File file = new File(systemFolder, systemFolderFiles[i]);
418                         if (!file.isFile()) {
419                             continue;
420                         }
421                         // We're looking for a file with a creator code of 'MACS' and
422                         // a type of 'FNDR'.  Only requiring the type results in non-Finder
423                         // applications being picked up on certain Mac OS 9 systems,
424                         // especially German ones, and sending a GURL event to those
425                         // applications results in a logout under Multiple Users.
426                         Object fileType = getFileType.invoke(null, new Object[] { file });
427                         if (FINDER_TYPE.equals(fileType.toString())) {
428                             Object fileCreator = getFileCreator.invoke(null, new Object[] { file });
429                             if (FINDER_CREATOR.equals(fileCreator.toString())) {
430                                 browser = file.toString();  // Actually the Finder, but that's OK
431                                 return browser;
432                             }
433                         }
434                     } catch (IllegalArgumentException iare) {
435                         browser = browser;
436                         errorMessage = iare.getMessage();
437                         return null;
438                     } catch (IllegalAccessException iae) {
439                         browser = null;
440                         errorMessage = iae.getMessage();
441                         return browser;
442                     } catch (InvocationTargetException ite) {
443                         browser = null;
444                         errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
445                         return browser;
446                     }
447                 }
448                 browser = null;
449                 break;
450             case MRJ_3_0:
451             case MRJ_3_1:
452                 browser = "";   // Return something non-null
453                 break;
454             case WINDOWS_NT:
455                 browser = "cmd.exe";
456                 break;
457             case WINDOWS_9x:
458                 browser = "command.com";
459                 break;
460             case OTHER:
461             default:
462                 browser = "netscape";
463                 break;
464         }
465         return browser;
466     }
467 
468     /***
469      * Attempts to open the default web browser to the given URL.
470      * @param url The URL to open
471      * @throws IOException If the web browser could not be located or does not run
472      */
473     public static void openURL(String url) throws IOException {
474         if (!loadedWithoutErrors) {
475             throw new IOException("Exception in finding browser: " + errorMessage);
476         }
477         Object browser = locateBrowser();
478         if (browser == null) {
479             throw new IOException("Unable to locate browser: " + errorMessage);
480         }
481         
482         switch (jvm) {
483             case MRJ_2_0:
484                 Object aeDesc = null;
485                 try {
486                     aeDesc = aeDescConstructor.newInstance(new Object[] { url });
487                     putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc });
488                     sendNoReply.invoke(browser, new Object[] { });
489                 } catch (InvocationTargetException ite) {
490                     throw new IOException("InvocationTargetException while creating AEDesc: " + ite.getMessage());
491                 } catch (IllegalAccessException iae) {
492                     throw new IOException("IllegalAccessException while building AppleEvent: " + iae.getMessage());
493                 } catch (InstantiationException ie) {
494                     throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage());
495                 } finally {
496                     aeDesc = null;  // Encourage it to get disposed if it was created
497                     browser = null; // Ditto
498                 }
499                 break;
500             case MRJ_2_1:
501                 Runtime.getRuntime().exec(new String[] { (String) browser, url } );
502                 break;
503             case MRJ_3_0:
504                 int[] instance = new int[1];
505                 int result = ICStart(instance, 0);
506                 if (result == 0) {
507                     int[] selectionStart = new int[] { 0 };
508                     byte[] urlBytes = url.getBytes();
509                     int[] selectionEnd = new int[] { urlBytes.length };
510                     result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes,
511                                             urlBytes.length, selectionStart,
512                                             selectionEnd);
513                     if (result == 0) {
514                         // Ignore the return value; the URL was launched successfully
515                         // regardless of what happens here.
516                         ICStop(instance);
517                     } else {
518                         throw new IOException("Unable to launch URL: " + result);
519                     }
520                 } else {
521                     throw new IOException("Unable to create an Internet Config instance: " + result);
522                 }
523                 break;
524             case MRJ_3_1:
525                 try {
526                     openURL.invoke(null, new Object[] { url });
527                 } catch (InvocationTargetException ite) {
528                     throw new IOException("InvocationTargetException while calling openURL: " + ite.getMessage());
529                 } catch (IllegalAccessException iae) {
530                     throw new IOException("IllegalAccessException while calling openURL: " + iae.getMessage());
531                 }
532                 break;
533             case WINDOWS_NT:
534             case WINDOWS_9x:
535                 // Add quotes around the URL to allow ampersands and other special
536                 // characters to work.
537                 Process process = Runtime.getRuntime().exec(new String[] { (String) browser,
538                                                                 FIRST_WINDOWS_PARAMETER,
539                                                                 SECOND_WINDOWS_PARAMETER,
540                                                                 THIRD_WINDOWS_PARAMETER,
541                                                                 '"' + url + '"' });
542                 // This avoids a memory leak on some versions of Java on Windows.
543                 // That's hinted at in <http://developer.java.sun.com/developer/qow/archive/68/>.
544                 try {
545                     process.waitFor();
546                     process.exitValue();
547                 } catch (InterruptedException ie) {
548                     throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
549                 }
550                 break;
551             case OTHER:
552                 // Assume that we're on Unix and that Netscape is installed
553                 
554                 // First, attempt to open the URL in a currently running session of Netscape
555                 process = Runtime.getRuntime().exec(new String[] { (String) browser,
556                                                     NETSCAPE_REMOTE_PARAMETER,
557                                                     NETSCAPE_OPEN_PARAMETER_START +
558                                                     url +
559                                                     NETSCAPE_OPEN_PARAMETER_END });
560                 try {
561                     int exitCode = process.waitFor();
562                     if (exitCode != 0) {    // if Netscape was not open
563                         Runtime.getRuntime().exec(new String[] { (String) browser, url });
564                     }
565                 } catch (InterruptedException ie) {
566                     throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
567                 }
568                 break;
569             default:
570                 // This should never occur, but if it does, we'll try the simplest thing possible
571                 Runtime.getRuntime().exec(new String[] { (String) browser, url });
572                 break;
573         }
574     }
575 
576     /***
577      * Methods required for Mac OS X.  The presence of native methods does not cause
578      * any problems on other platforms.
579      */
580     private native static int ICStart(int[] instance, int signature);
581     private native static int ICStop(int[] instance);
582     private native static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len,
583                                             int[] selectionStart, int[] selectionEnd);
584 }