/* * * * 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.midp.midlet; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; import com.sun.j2me.security.AccessController; import com.sun.midp.security.Permissions; import com.sun.midp.security.SecurityToken; import com.sun.midp.log.Logging; import com.sun.midp.log.LogChannels; import com.sun.midp.main.CldcMIDletStateListener; /** * The MIDletStateHandler starts and controls MIDlets through the lifecycle * states. * MIDlets are created using its no-arg Constructor. Once created * a MIDlet is sequenced through the <code>ACTIVE</code>, * <code>PAUSED</code>, and <code>DESTROYED</code> states. * <p> * The MIDletStateHandler is a singleton for the suite being run and * is retrieved with getMIDletStateHandler(). This allow the * MIDletStateHandler to be the anchor of trust internally for the MIDP API, * restricted methods can obtain the MIDletStateHandler for a MIDlet suite * inorder to check the properties and actions of a suite. * Because of this, there MUST only be one a MIDlet suite per * MIDletStateHandler. In addition a method can assume that the application * manager is the caller if there is no suite started.</p> * <p> * The MIDlet methods are protected in the javax.microedition.midlet package * so the MIDletStateHandler can not call them directly. The MIDletState * object and * MIDletTunnel subclass class allow the MIDletStateHandler to hold the state * of the * MIDlet and to invoke methods on it. The MIDletState instance is created * by the MIDlet when it is constructed. * <p> * This implementation of the MIDletStateHandler introduces * extra internal MIDlet states to allow processing of MIDlet * requested state changes in the control thread and serialized with * other state changes. The additional states are: * <UL> * <LI> <code>ACTIVE_PENDING</code> - The MIDlet is still PAUSED but * will be <code>ACTIVE</code> after startApp is called when the state is * next processed. * <LI> <code>PAUSE_PENDING</code> - The MIDlet is still ACTIVE but * will be <code>PAUSED</code> after pauseApp is called when the state is * next processed. * <LI> <code>DESTROY_PENDING</code> - Indicates that the MIDlet needs * to be <code>DESTROYED</code>. The MIDlet's destroyApp has not yet been * called. * </UL> * The MIDletStateHandler loops, looking for MIDlets that require state changes * and making the requested changes including calling methods in * the MIDlet to make the change. * <p> * When a MIDlet's state is changed to <code>ACTIVE</code>, * <code>PAUSED</code>, or <code>DESTROYED</code> the MIDlet state listener * is notified of the change, which in turn sends the notification onto * the central AMS. * * @see MIDlet * @see MIDletPeer * @see MIDletLoader * @see MIDletStateHandler */ public class MIDletStateHandler { /** the current MIDlet suite. */ private MIDletSuite midletSuite; /** array of MIDlets. */ private MIDletPeer[] midlets; /** current number of MIDlets [0..n-1]. */ private int nmidlets; /** next index to be scanned by selectByPriority. */ private int scanIndex; /** The event handler of all MIDlets in an Isolate. */ private static MIDletStateHandler stateHandler; /** The listener for the state of all MIDlets in an Isolate. */ private static CldcMIDletStateListener listener; /** Serializes the creation of MIDlets. */ private static Object createMIDletLock = new Object(); /** New MIDlet peer waiting for the next MIDlet created to claim it. */ private static MIDletPeer newMidletPeer; /** MIDlet peer for MIDlet being constructed but not registered yet. */ private MIDletPeer underConstructionPeer; /** * Construct a new MIDletStateHandler object. */ private MIDletStateHandler() { nmidlets = 0; // start with 5 empty slots, we will add more if needed midlets = new MIDletPeer[5]; } /** * Gets the MIDletStateHandler that manages the lifecycle states of * MIDlets running in an Isolate. * <p> * If the instance of the MIDletStateHandler has already been created * it is returned. If not it is created. * The instance becomes the MIDletStateHandler for this suite. * <p> * The fact that there is one handler per Isolate * is a security feature. Also a security feature, is that * getMIDletStateHandler is * static, so API can find out what suite is calling, if in the future * multiple suites can be run in the same VM, the MIDlet state handler * for each suite * should be loaded in a different classloader or have some other way * having multiple instances of static class data. * * @return the MIDlet state handler for this Isolate */ public static synchronized MIDletStateHandler getMidletStateHandler() { /* * If the midlet state handler has not been created, create one now. */ if (stateHandler == null) { /* This is the default scheduler class */ stateHandler = new MIDletStateHandler(); } return stateHandler; } /** * Initializes MIDlet State Handler. * * @param token security token for initilaization * @param theMIDletStateListener processes MIDlet states in a * VM specific way * @param theMidletLoader loads a MIDlet in a VM specific way * @param thePlatformRequestHandler the platform request handler */ public void initMIDletStateHandler( SecurityToken token, CldcMIDletStateListener theMIDletStateListener) { token.checkIfPermissionAllowed(Permissions.AMS); listener = theMIDletStateListener; MIDletPeer.initClass(this, listener); } /** * Starts a MIDlet from outside of the package. * * @param classname name of MIDlet class * @param displayName name to show the user * * @exception SecurityException if the suite does not have the * AMS permission. * @exception ClassNotFoundException is thrown, if the MIDlet main class is * not found * @exception InstantiationException is thrown, if the MIDlet can not be * created * @exception IllegalAccessException is thrown, if the MIDlet is not * permitted to perform a specific operation */ public void startMIDlet(String classname, String displayName) throws ClassNotFoundException, InstantiationException, IllegalAccessException { startMIDlet(0, classname, displayName); } /** * Starts a MIDlet from outside of the package. * <p> * Method requires com.sun.midp.ams permission. * * @param externalAppId ID of given by an external application manager * @param classname name of MIDlet class * @param displayName name to show the user * * @exception SecurityException if the suite does not have the * AMS permission. * @exception ClassNotFoundException is thrown, if the MIDlet main class is * not found * @exception InstantiationException is thrown, if the MIDlet can not be * created * @exception IllegalAccessException is thrown, if the MIDlet is not * permitted to perform a specific operation */ public void startMIDlet(int externalAppId, String classname, String displayName) throws ClassNotFoundException, InstantiationException, IllegalAccessException { AccessController.checkPermission(Permissions.AMS_PERMISSION_NAME); createAndRegisterMIDlet(externalAppId, classname); } /** * Starts a MIDlet from outside of the package. * * @param token security token of the caller * @param classname name of MIDlet class * @param displayName name to show the user * * @exception SecurityException if the caller does not have the * AMS permission. * @exception ClassNotFoundException is thrown, if the MIDlet main class is * not found * @exception InstantiationException is thrown, if the MIDlet can not be * created * @exception IllegalAccessException is thrown, if the MIDlet is not * permitted to perform a specific operation */ public void startMIDlet(SecurityToken token, String classname, String displayName) throws ClassNotFoundException, InstantiationException, IllegalAccessException { startMIDlet(token, 0, classname, displayName); } /** * Starts a MIDlet from outside of the package. * * @param token security token of the caller * @param externalAppId ID of given by an external application manager * @param classname name of MIDlet class * @param displayName name to show the user * * @exception SecurityException if the caller does not have the * AMS permission. * @exception ClassNotFoundException is thrown, if the MIDlet main class is * not found * @exception InstantiationException is thrown, if the MIDlet can not be * created * @exception IllegalAccessException is thrown, if the MIDlet is not * permitted to perform a specific operation */ public void startMIDlet(SecurityToken token, int externalAppId, String classname, String displayName) throws ClassNotFoundException, InstantiationException, IllegalAccessException { token.checkIfPermissionAllowed(Permissions.AMS); createAndRegisterMIDlet(externalAppId, classname); } /** * Gets the class name first midlet in the list of running MIDlets. * * @return the classname or null if no midlet are running */ public String getFirstRunningMidlet() { synchronized (this) { if (nmidlets <= 0) { return null; } return midlets[0].midlet.getClass().getName(); } } /** * Registers a MIDlet being constructed. * * @param midlet to be registered with this state handler */ private void register(MIDlet midlet) { synchronized (this) { MIDletPeer state = MIDletPeer.getMIDletPeer(midlet); /* * If a MIDlet of the same class is already running * Make the existing MIDlet current so that startSuite() * will run it */ int i = findMIDletByClass(state); if (i >= 0) { state.setState(MIDletPeer.DESTROY_PENDING); // Fall into adding it to the list so destroyApp // can be called at a reasonable time. } // Grow the list if necessary if (nmidlets >= midlets.length) { MIDletPeer[] n = new MIDletPeer[nmidlets+5]; System.arraycopy(midlets, 0, n, 0, nmidlets); midlets = n; } // Add it to the end of the list midlets[nmidlets++] = state; // MIDlet peer is registered now underConstructionPeer = null; this.notify(); } } /** * Creates and register MIDlet with VM notification * of the MIDlet's startup phase. * * @param externalAppId ID of given by an external application manager * @param classname name of MIDlet class * * @exception ClassNotFoundException if the MIDlet class is * not found * @exception InstantiationException if the MIDlet cannot be * created * @exception IllegalAccessException if the MIDlet is not * permitted to perform a specific operation */ private void createAndRegisterMIDlet(int externalAppId, String classname) throws ClassNotFoundException, InstantiationException, IllegalAccessException { listener.midletPreStart(getMIDletSuite(), classname); register(createMIDlet(externalAppId, classname)); } /** * Provides a object with a mechanism to retrieve * <code>MIDletSuite</code> being run. * * @return MIDletSuite being run */ public MIDletSuite getMIDletSuite() { return midletSuite; } /** * Runs MIDlets until there are none. * Handle any pending state transitions of any MIDlet. * If there are none, wait for transitions. * * @param exceptionHandler the handler for midlet execution exceptions. * @param aMidletSuite the current midlet suite * @param externalAppId ID of given by an external application manager * @param classname name of MIDlet class * * @exception ClassNotFoundException is thrown, if the MIDlet main class is * not found * @exception InstantiationException is thrown, if the MIDlet can not be * created * @exception IllegalAccessException is thrown, if the MIDlet is not * permitted to perform a specific operation */ public void startSuite(MIDletSuiteExceptionListener exceptionHandler, MIDletSuite aMidletSuite, int externalAppId, String classname) throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (midletSuite != null) { throw new RuntimeException( "There is already a MIDlet Suite running."); } midletSuite = aMidletSuite; createAndRegisterMIDlet(externalAppId, classname); /* * Until there are no MIDlets * Scan all the MIDlets looking for state changes. */ while (nmidlets > 0) { try { MIDletPeer curr; int state; /* * A MIDlet can change the MIDlet concurrently. * the MIDlet state handler this is used to * synchronize these changes. However any calls to outside of * this package should NOT be done holding * "this". * For this reason there are 2 phases each with a switch * statement to process a state. * * The state is obtained and changed before the work is * done so that when "this" is released to * perform external calls for that state, any state change done * by the MIDlet concurrently are not lost. */ synchronized (this) { /* * Find the highest priority state of any MIDlet and * process, but do not hold the lock while processing * to avoid deadlocks with LCDUI and event handling. * Perform state changes with a lock so * no state changes are lost. */ curr = selectByPriority(); state = curr.getState(); switch (state) { case MIDletPeer.ACTIVE: // fall through case MIDletPeer.PAUSED: // Wait for some change in the state of a MIDlet // that needs attention try { this.wait(); } catch (InterruptedException e) { if (Logging.REPORT_LEVEL <= Logging.WARNING) { Logging.report(Logging.WARNING, LogChannels.LC_AMS, "InterruptedException " + "during mutex wait"); } } continue; case MIDletPeer.ACTIVE_PENDING: // Start the MIDlet curr.setStateWithoutNotify(MIDletPeer.ACTIVE); break; case MIDletPeer.PAUSE_PENDING: // The system wants the MIDlet paused curr.setStateWithoutNotify(MIDletPeer.PAUSED); break; case MIDletPeer.DESTROY_PENDING: curr.setStateWithoutNotify(MIDletPeer.DESTROYED); break; case MIDletPeer.DESTROYED: unregister(curr); break; default: throw new Error("Illegal MIDletPeer state " + curr.getState()); } } /** perform work that may block outside of "this" */ switch (state) { case MIDletPeer.ACTIVE_PENDING: try { listener.preActivated(getMIDletSuite(), curr.getMIDlet().getClass().getName()); curr.startApp(); } catch (Throwable ex) { if (Logging.TRACE_ENABLED) { Logging.trace(ex, "startApp threw an Exception"); } curr.setState(MIDletPeer.DESTROY_PENDING); exceptionHandler.handleException(ex); break; } /* * The actual state of the MIDlet is already active. * But any notifications done after startApp call. */ listener.midletActivated(getMIDletSuite(), curr.getMIDlet()); break; case MIDletPeer.PAUSE_PENDING: try { curr.pauseApp(); } catch (Throwable ex) { if (Logging.TRACE_ENABLED) { Logging.trace(ex, "pauseApp threw an Exception"); } curr.setState(MIDletPeer.DESTROY_PENDING); exceptionHandler.handleException(ex); break; } /* * The actual state of the MIDlet is already paused. * But any notifications done after pauseApp() call. */ listener.midletPaused(getMIDletSuite(), curr.getMIDlet().getClass().getName()); break; case MIDletPeer.DESTROY_PENDING: // If the MIDlet is in the DESTROY_PENDING state // call its destroyApp method to clean it up. try { // Tell the MIDlet to cleanup. curr.destroyApp(true); } catch (MIDletStateChangeException ex) { if (Logging.REPORT_LEVEL <= Logging.WARNING) { Logging.report(Logging.WARNING, LogChannels.LC_AMS, "destroyApp threw a " + "MIDletStateChangeException"); } exceptionHandler.handleException(ex); } catch (Throwable ex) { if (Logging.TRACE_ENABLED) { Logging.trace(ex, "destroyApp threw an Exception"); } exceptionHandler.handleException(ex); } break; case MIDletPeer.DESTROYED: listener.midletDestroyed(getMIDletSuite(), curr.getMIDlet().getClass().getName(), curr.getMIDlet()); break; } } catch (Throwable ex) { if (Logging.TRACE_ENABLED) { Logging.trace(ex, "Exception in startSuite"); } exceptionHandler.handleException(ex); } } } /** * Destroys all running MIDlets in this suite only. This method is only * used by the push registry in single VM mode. */ public void destroySuite() { synchronized (this) { for (int i = 0; i < nmidlets; i++) { if (midlets[i].getState() != MIDletPeer.DESTROYED) { midlets[i]. setStateWithoutNotify(MIDletPeer.DESTROY_PENDING); } } this.notify(); } } /** * Checks if the named <code>MIDlet</code> has already been instantiated. * @param name class name of <code>MIDlet</code> to test if * currently run * @return <code>true</code> if an instance of the MIDlet is already * running */ public boolean isRunning(String name) { boolean found = false; synchronized (this) { if (underConstructionPeer != null && underConstructionPeer.getMIDlet(). getClass().getName().equals(name)) { found = true; } else { for (int i = 0; i < nmidlets; i++) { if (midlets[i].getMIDlet(). getClass().getName().equals(name)) { // found only if has not been destroyed found = (midlets[i].getState() != MIDletPeer.DESTROYED); break; } } } } return found; } /** * Gets MIDlet event consumer of the named <code>MIDlet</code>. * * @param token security token for authorizing the caller * @param name class name of <code>MIDlet</code> * * @return reference of the MIDlet event consumer */ public MIDletEventConsumer getMIDletEventConsumer(SecurityToken token, String name) { token.checkIfPermissionAllowed(Permissions.AMS); synchronized (this) { for (int i = 0; i < nmidlets; i++) { if (midlets[i].getMIDlet().getClass().getName().equals(name)) { return midlets[i]; } } } return null; } /** * Looks through the current MIDlets and select one to * be processed. * <p>Note: that this method is called while synchronized on "this" * @return the MIDlet to process next */ private MIDletPeer selectByPriority() { MIDletPeer found = null; // Chosen MIDletPeer int state = -1; // the state of the chosen MIDlet /* * Find the most desirable MIDlet based on its state * The higher state values are preferred because they * are needed for cleanup. */ for (int i = nmidlets-1; i >= 0; i--) { // make sure index is inside current array, favoring the end if (scanIndex < 0 || scanIndex >= nmidlets) scanIndex = nmidlets-1; // Pick this MIDlet if the state is higher priority int s = midlets[scanIndex].getState(); if (s > state) { found = midlets[scanIndex]; state = s; } scanIndex--; } return found; } /** * Removes a MIDlet from the list if it is there, * otherwise ignore the request. * Call only while synchronized on "this". * @param m the MIDlet to remove */ private void unregister(MIDletPeer m) { // Find it in the list and switch the last one for it. for (int i = 0; i < nmidlets; i++) { if (m == midlets[i]) { // Switch the last MIDlet into that offset. midlets[i] = midlets[nmidlets-1]; // null out from array and remove from map to allow for GC midlets[--nmidlets] = null; break; } } } /** * Finds a MIDlet in the list by it class. * Only a single MIDlet of a class can be active at * a time. * Must be called synchronized on "this". * @param m the MIDlet to find * @return the index in the array of MIDlets. * return -1 if the MIDlet is not found. */ private int findMIDletByClass(MIDletPeer m) { // Find it in the list for (int i = 0; i < nmidlets; i++) { if (m.getMIDlet().getClass() == midlets[i].getMIDlet().getClass()) { return i; } } return -1; } /** * Creates a MIDlet. * * @param externalAppId ID of given by an external application manager * @param classname name of MIDlet class * * @return newly created MIDlet * * @exception ClassNotFoundException if the MIDlet class is * not found * @exception InstantiationException if the MIDlet cannot be * created * @exception IllegalAccessException if the MIDlet is not * permitted to perform a specific operation */ private MIDlet createMIDlet(int externalAppId, String classname) throws ClassNotFoundException, InstantiationException, IllegalAccessException { MIDlet midlet = null; synchronized (createMIDletLock) { /* * Just in case there is a hole we have not found. * Make sure there is not a new MIDlet state already created. */ if (newMidletPeer != null) { throw new SecurityException("Recursive MIDlet creation"); } newMidletPeer = new MIDletPeer(); underConstructionPeer = newMidletPeer; try { /* * We can send a MIDlet create event because the peer that * the AMS uses has been created. */ listener.midletCreated(getMIDletSuite(), classname, externalAppId); try { Class midletClass = Class.forName(classname); if (!Class.forName("javax.microedition.midlet.MIDlet").isAssignableFrom(midletClass)) { throw new InstantiationException("Class not a MIDlet"); } midlet = (MIDlet)midletClass.newInstance(); return midlet; } finally { if (midlet == null) { /* * The MIDlet was not constructed, send destroy * notification to remove the peer from any lists. */ listener.midletDestroyed(getMIDletSuite(), classname, null); } } } finally { /* Make sure the creation window is closed. */ newMidletPeer = null; } } } /** * Called by the MIDlet constructor to a new MIDletPeer object. * * @param token security token for authorizing the caller * @param m the MIDlet for which this state is being created; * must not be <code>null</code>. * @return the preallocated MIDletPeer for the MIDlet being constructed by * {@link #createMIDlet} * * @exception SecurityException AMS permission is not granted and * if is constructor is not being called in the context of * <code>createMIDlet</code>. */ public static MIDletPeer newMIDletPeer(SecurityToken token, MIDlet m) { token.checkIfPermissionAllowed(Permissions.MIDP); synchronized (createMIDletLock) { MIDletPeer temp; if (newMidletPeer == null) { throw new SecurityException( "MIDlet not constructed by createMIDlet."); } temp = newMidletPeer; newMidletPeer = null; temp.midlet = m; return temp; } } /** * Retrieves current state of the MIDlet given. * @param midlet the MIDlet of interest * @return the MIDlet state as defined in MIDletPeer class */ public static int getMIDletState(MIDlet midlet) { return MIDletPeer.getMIDletPeer(midlet).getState(); } }