/* * * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.j2me.content; import java.util.Vector; import java.util.Hashtable; import com.sun.midp.security.Permissions; import com.sun.j2me.security.Token; import com.sun.midp.security.SecurityToken; import com.sun.midp.midlet.MIDletSuite; import com.sun.midp.main.MIDletSuiteUtils; import com.sun.midp.midlet.MIDletStateHandler; import com.sun.midp.midletsuite.MIDletSuiteImpl; import com.sun.midp.midletsuite.MIDletSuiteStorage; import com.sun.midp.midletsuite.MIDletSuiteLockedException; import com.sun.midp.midletsuite.MIDletSuiteCorruptedException; import com.sun.midp.events.NativeEvent; import com.sun.midp.events.EventTypes; import com.sun.midp.events.EventQueue; import com.sun.midp.io.Util; import javax.microedition.content.ContentHandlerException; /** * Each AppProxy instance provides access to the AMS information * and functions for a running or installed application. * This class must be replaced for each platform/profile combination and * be integrated with the appropriate Application Management Software. * <p> * The AppProxy class is *only* available within * this package for security purposes. * An instance exists for the current application and can * be retrieved for any installed application. * The following methods provide functions needed by CHAPI: * <ul> * <li>{@link #getCurrent} - the current application and the main * class within it. * <li>{@link #forApp} - the AppProxy for a named Jar ID and * classname. For MIDP, they are the suiteID of the storage and * MIDlet within the JAR. * <li>{@link #getStorageID} - the storage ID for this AppProxy. * <li>{@link #getClassname} - the classname for this AppProxy. * <li>{@link #getApplicationID} - the CHAPI defined unique identifier * for the application * <li>{@link #getApplicationName} = a user friendly name for this application. * <li>{@link #getAuthority} - the authority under which the application * is granted permissions. For MIDP, the subject field of the signing * certificate. * <li>{@link #getProperty} - access to the properties in the manifest or * application descriptor. * <li>{@link #getVersion} - the version number of this application. * <li>{@link #getDefaultID} - the default applicationID if none is provided * in the manifest or application descriptor. * <li>{@link #checkRegisterPermission} - method to check if the caller * is allowed to register or unregister handlers. * <li>{@link #checkAPIPermission} - method to check if the caller has * permission to use internal APIs. Caller must have an appropriate * security token (depending on the platform) * <li>{@link #isRegistered} - used to check if the application is * registered as appropriate for the platform. For MIDP, there * is a MIDlet-n attribute in manifest or JAD. * <li>{@link #verifyApplication} - Verify that a named class is the * correct type and access to be launched as an application in the * profile. For MIDP, the class must extend MIDlet. * <li>{@link #launch} - Request this application be launched if it is * not already running. Return a boolean indicating if the current * application MUST voluntarily exit before the launched app can run. * <li>{@link #requestForeground} - ask the "window manager" to give the * foreground to the requested application. * < * </ul> */ class AppProxy extends CLDCAppID { /** This class has a different security domain than the MIDlet suite */ private static SecurityToken classSecurityToken; /** The current AppProxy. */ private static AppProxy currentApp; /** The log flag to enable informational messages. */ static final Logger LOGGER = null; // new Logger(); private static final boolean isInSvmMode = isInSvmMode(); /** The known AppProxy instances. Key is classname. */ protected Hashtable appmap; /** The mutex used to avoid corruption between threads. */ protected static final Object mutex = new Object(); /** The MIDlet suite for this app. */ protected final MIDletSuite msuite; // /** The storageId (suiteId) for this application. */ // protected final int storageId; // /** The classname of the application. */ // protected String classname; protected String version, authority; /** The application name. */ private String applicationName; /** The ApplicationID, (same a suiteId). */ private String applicationID; /** The application is registered. */ private boolean isRegistered; /** MIDlet property for the suite version. */ static final String VERSION_PROP = "MIDlet-Version"; /** MIDlet property for the suite vendor. */ static final String VENDOR_PROP = "MIDlet-Vendor"; static final int EXTERNAL_SUITE_ID = MIDletSuite.UNUSED_SUITE_ID; // native methods static native boolean isInSvmMode(); static native void midletIsAdded( int suiteId, String className ); static native boolean isMidletRunning( int suiteId, String className ); static native void midletIsRemoved( int suiteId, String className ); static native boolean isSuiteRunning( int suiteId ); /** * Sets the security token used for privileged operations. * The token may only be set once. * @param token a Security token */ static void setSecurityToken(Object token) { token.getClass(); // null pointer check if (classSecurityToken != null) { throw new SecurityException(); } classSecurityToken = (SecurityToken)token; } static ApplicationID createAppID() { return new CLDCAppID(); } /** * Gets the AppProxy for the currently running application. * @return the current application. */ static AppProxy getCurrent() { synchronized (mutex) { if (currentApp == null) { MIDletStateHandler mh = MIDletStateHandler.getMidletStateHandler(); MIDletSuite msuite = mh.getMIDletSuite(); try { currentApp = (msuite != null) ? new AppProxy(msuite, msuite.getID(), mh.getFirstRunningMidlet(), null).verify() : new AppProxy(MIDletSuite.INTERNAL_SUITE_ID, ""); } catch (ClassNotFoundException cnfe) { return null; } } } return currentApp; } class VAGetter extends MIDletSuiteUser { void use(MIDletSuite msuite) { if( msuite instanceof MIDletSuiteImpl ){ try { authority =((MIDletSuiteImpl)msuite).getInstallInfo().getCA(); } catch (RuntimeException e) {} } version = msuite.getProperty(VERSION_PROP); } }; /** * Construct an AppProxy with the specified MIDletSuite. * * @param msuite the MIDletSuite to initialize. * @param classname the classname of a copackaged application. * @param appmap a Hashtable to keep track of instances; may be null * @exception ClassNotFoundException if the <code>classname</code> * is not present * @exception IllegalArgumentException if classname is not * a valid application */ protected AppProxy(MIDletSuite msuite, int suiteID, String classname, Hashtable appmap) throws ClassNotFoundException { super( suiteID, classname ); if (appmap == null) { // Allocate a Hashtable if needed appmap = new Hashtable(); } this.msuite = msuite; new VAGetter().execute(); this.appmap = appmap; if (LOGGER != null) LOGGER.println("AppProxy created: " + this); } protected AppProxy verify() throws ClassNotFoundException { if(className != null){ verifyApplication(className); initAppInfo(new MIDletSuiteUser()); appmap.put(className, this); if (LOGGER != null) LOGGER.println("AppProxy verified: ID " + super.toString() ); } return this; } /** * Construct an AppProxy with the specified suiteId, classname. * This is just a placeholder to make launch work. * * @param storageId the suiteId * @param classname the classname */ protected AppProxy(int suiteID, String classname) { super( suiteID, classname ); msuite = null; initAppInfo( new VAGetter() ); if (LOGGER != null) { LOGGER.println("AppProxy created: " + classname); } } /** * Gets the AppProxy for an application class in the current bundle. * @param classname the name of the application class * @return the AppProxy for classname; <code>null</code> if not * a valid application (MIDlet) * @exception ClassNotFoundException if the <code>classname</code> * is not present * @exception IllegalArgumentException if classname is * not a valid application */ AppProxy forClass(String classname) throws ClassNotFoundException { AppProxy curr = null; synchronized (mutex) { // Check if class already has a AppProxy curr = (AppProxy)appmap.get(classname); if (curr == null) { // Create a new instance // throws ClassNotFoundException and IllegalArgumentException curr = new AppProxy(msuite, suiteID, classname, appmap).verify(); } } return curr; } /** * Gets the AppProxy for an storageID and classname. * @param storageId the storageId (suiteId) * @param classname the name of the application class * @return the AppProxy for suiteId, classname; * <code>null</code> if not a valid application (MIDlet) * @exception ClassNotFoundException if the <code>classname</code> * is not present * @exception IllegalArgumentException if classname is not * a valid application */ AppProxy forApp(ApplicationID appID) throws ClassNotFoundException { // Check in the current suite CLDCAppID id = CLDCAppID.from(appID); if (id.suiteID == this.suiteID) { return forClass(id.className); } // Create a new instance return new AppProxy(id.suiteID, id.className); } // /** // * Gets the storage ID of this application. // * The ID uniquely identifies the package/application bundle. // * @return the application ID. // */ // int getStorageId() { // return storageId; // } // // /** // * Gets the classname of this application. // * @return the classname // */ // String getClassname() { // return classname; // } /** * Gets the user friendly application name. * @return the user friendly application name */ String getApplicationName() { return applicationName; } /** * Gets the CHAPI application ID for this application. * @return the CHAPI application ID. */ String getApplicationID() { return applicationID; } /** * Gets the version string for the application. * @return the version */ String getVersion() { return version; } /** * Gets the Trusted authority that authenticated this application. * <p> * For MIDP, this is the CA of the signer. * If exception is thrown during getting authorization * this methods ignores it and returns null. * @return the authority. */ String getAuthority() { return authority; } /** * Gets true if the application is a registered application. * <p> * for MIDP, this means there was a MIDlet-n attribute. * @return true if this application is registered */ boolean isRegistered() { return isRegistered; } /** * Gets a property from the manifest or application descriptor. * @param key the name of the property to retrieve * @return the value of the property or <code>null</code> */ String getProperty(final String key) { class user extends MIDletSuiteUser { String value; void use(MIDletSuite msuite) { value = msuite.getProperty(key); } }; return ((user)new user().execute()).value; } /** * Check the permission to register or unregister. * @param reason the reason for the permission check * @exception SecurityException if not allowed */ final void checkRegisterPermission(final String reason) { new MIDletSuiteUser(){ void use(MIDletSuite msuite) { try { msuite.checkForPermission( /*Permissions.CHAPI_REGISTER*/ "javax.microedition.content.ContentHandler", getApplicationName(), reason); } catch (InterruptedException ie) { throw new SecurityException("interrupted"); } } }.execute(); } /** * Check if the internal API use is allowed. * @param securityToken a generic security token * @exception SecurityException thrown if internal API use not allowed */ final static void checkAPIPermission(Token token) { SecurityToken securityToken = token.getSecurityToken(); if (securityToken != null) { securityToken.checkIfPermissionAllowed(Permissions.MIDP/*_PERMISSION_NAME*/); } else { MIDletSuite msuite = MIDletStateHandler.getMidletStateHandler().getMIDletSuite(); if (msuite != null) { msuite.checkIfPermissionAllowed(Permissions.AMS_PERMISSION_NAME); } } } /** * Request the transition of the foreground to this application * from the invoking application. * @param fromApp the invoking application * @param toApp the target application */ static void requestForeground(ApplicationID fromApp, ApplicationID toApp) { if( LOGGER != null ) LOGGER.println( "requestForeground: " + fromApp + " -> " + toApp); NativeEvent event = new NativeEvent(EventTypes.FOREGROUND_TRANSFER_EVENT); event.intParam1 = CLDCAppID.from(fromApp).suiteID; event.stringParam1 = CLDCAppID.from(fromApp).className; event.intParam2 = CLDCAppID.from(toApp).suiteID; event.stringParam2 = CLDCAppID.from(toApp).className; int amsIsolateId = MIDletSuiteUtils.getAmsIsolateId(); EventQueue eventQueue = EventQueue.getEventQueue(classSecurityToken); eventQueue.sendNativeEventToIsolate(event, amsIsolateId); } /* * * The stronger variant for request the transition of * the foreground to this application. * @param targetSuiteId the target suiteId * @param targetClassname the target classname * / static void requestForeground(int targetSuiteId, String targetClassname) { NativeEvent event = new NativeEvent(EventTypes.SET_FOREGROUND_BY_NAME_REQUEST); event.intParam1 = targetSuiteId; event.stringParam1 = targetClassname; int amsIsolateId = MIDletSuiteUtils.getAmsIsolateId(); EventQueue eventQueue = EventQueue.getEventQueue(classSecurityToken); eventQueue.sendNativeEventToIsolate(event, amsIsolateId); } /**/ /** * Launch this application. * Don't launch another application unless * the execute allows this application to continue after * the launch. * <p> * In SVM, (sequential applications) only the first * execute matters; later ones should not override the * first. All pending Invocations are queued in InvocationStore * so they will not be lost. When MIDlets exit, another * application will be selected from those pending. * * @param displayName name to show to the user of what to launch * @return <code>true</code> if the application is started. */ boolean launch(String displayName) { if( isMidletRunning(suiteID, className) ) return true; if( LOGGER != null ) LOGGER.println("AppProxy.launch(): " + (MIDletSuiteUtils.isAmsIsolate()?"":"NOT ") + "isAmsIsolate()"); if( isInSvmMode && !MIDletSuiteUtils.isAmsIsolate() ) return false; if( suiteID != MIDletSuite.INTERNAL_SUITE_ID && isSuiteRunning(suiteID) ) return false; if( LOGGER != null ) LOGGER.println("AppProxy.launch(): send 'launch' request {" + suiteID + ", '" + className + "'}"); // always launch an application in background mode return MIDletSuiteUtils.execute(classSecurityToken, suiteID, className, displayName); } /** * Verify that the classname is a valid application. * It must extend MIDlet. * @param classname the application class * * @exception ClassNotFoundException is thrown if the class cannot be found * @exception IllegalArgumentException if the classname is null or empty * or does not implement the lifecycle of a MIDlet. */ protected void verifyApplication(String classname) throws ClassNotFoundException { /* check the classname for null and get the class */ Class appClass = Class.forName(classname); Class midletClass = Class.forName("javax.microedition.midlet.MIDlet"); if ((!midletClass.isAssignableFrom(appClass)) || appClass == midletClass) { throw new IllegalArgumentException("Class '" + classname + "' is not a MIDlet"); } } /** * Initialize application name and application ID * from the attributes. */ protected void initAppInfo( final MIDletSuiteUser user ) { // Check if it is an internal MIDlet if (suiteID == MIDletSuite.INTERNAL_SUITE_ID) { applicationName = className.substring(className.lastIndexOf('.') + 1); applicationID = "system"; isRegistered = true; return; } new MIDletSuiteUser() { void use( MIDletSuite msuite ){ user.use( msuite ); // Check if a registered MIDlet String[] minfo = getMIDletInfo(msuite, className); // if a MIDlet, set the application name and application ID if (minfo != null) { applicationName = minfo[0]; applicationID = minfo[2]; isRegistered = true; } // Fill in defaults for appName and applicationID if (applicationName == null || applicationName.length() == 0) { applicationName = msuite.getProperty( MIDletSuiteImpl.SUITE_NAME_PROP); } if (applicationID == null || applicationID.length() == 0) { applicationID = getDefaultID(msuite); } } }.execute(); } /** * Gets the content handler ID for the current application. * The ID uniquely identifies the application which contains the * content handler. * The application ID is assigned when the application is installed. * <p> * All installed applications have vendor and name; * * @return the ID; MUST NOT be <code>null</code> */ String getDefaultID() { class user extends MIDletSuiteUser { String defID; void use(MIDletSuite msuite) { defID = getDefaultID(msuite); } }; return ((user)new user().execute()).defID; } private String getDefaultID( MIDletSuite suite ) { StringBuffer sb = new StringBuffer(80); String s = suite.getProperty(VENDOR_PROP); sb.append((s != null) ? s : "internal"); sb.append('-'); s = suite.getProperty(MIDletSuiteImpl.SUITE_NAME_PROP); sb.append((s != null) ? s : "system"); sb.append('-'); sb.append(className); return sb.toString().replace(' ', '_'); } /** * Get the MIDletInfo for the named MIDlet. * @param suite the MIDlet suite to look in for the midlet * @param classname the class name to look for * @return an array of Strings, name, icon, ID * null if there is no matching MIDlet-n. */ private static String[] getMIDletInfo(MIDletSuite suite, String classname) { for (int i = 1; ; i++) { String midletn = "MIDlet-".concat(Integer.toString(i)); String attr = suite.getProperty(midletn); if (attr == null) { break; // break out of loop, not found } Vector args = Util.getCommaSeparatedValues(attr); if (args.size() < 3) { // Not enough args to be legit continue; } if (!classname.equals(args.elementAt(2))) { continue; } String[] values = new String[args.size()]; args.copyInto(values); String ID = suite.getProperty(midletn.concat("-ID")); values[2] = ID; return values; } return null; } /** * Starts native content handler. * @param handler Content handler to be executed. * @return true if invoking app should exit. * @exception ContentHandlerException if no such handler ID in the Registry * or native handlers execution is not supported. */ static boolean launchNativeHandler(String handlerID) throws ContentHandlerException { int result = launchNativeHandler0(handlerID); if (result < 0) { throw new ContentHandlerException( "Unable to launch platform handler", ContentHandlerException.NO_REGISTERED_HANDLER); } return (result > 0); } /** * Informs platform about finishing of processing platform's request * @param invoc finished invocation * @return should_exit flag for the invocation handler */ static boolean platformFinish(int tid) { return platformFinish0(tid); } /** * Starts native content handler. * @param handlerId ID of the handler to be executed * @return result status: * <ul> * <li> 0 - LAUNCH_OK * <li> > 0 - LAUNCH_OK_SHOULD_EXIT * <li> < 0 - error * </ul> */ private static native int launchNativeHandler0(String handlerId); private static native boolean platformFinish0(int tid); /** * Create a printable representation of this AppProxy. * @return a printable string */ public String toString() { if (LOGGER != null) { return super.toString() + ", registered: " + isRegistered + ", name: " + applicationName + ", ID: " + applicationID; } else { return super.toString(); } } protected class MIDletSuiteUser { void use( MIDletSuite msuite ){}; MIDletSuiteUser execute() { if( msuite != null ){ if (LOGGER != null) LOGGER.println("msuite isn't null: " + msuite); use( msuite ); } else if( suiteID != MIDletSuite.INTERNAL_SUITE_ID ){ try { MIDletSuite suite = MIDletSuiteStorage. getMIDletSuiteStorage(classSecurityToken). getMIDletSuite(suiteID, false); if (LOGGER != null) LOGGER.println("use msuite " + suite); if( suite != null ){ try { use(suite); } finally { suite.close(); } } } catch (MIDletSuiteLockedException msle) { if (LOGGER != null) { LOGGER.log("AppProxy initialization failed", msle); } } catch (MIDletSuiteCorruptedException msce) { if (LOGGER != null) { LOGGER.log("AppProxy initialization failed", msce); } } } return this; } } }