FileDocCategorySizeDatePackage
MIDletStateHandler.javaAPI DocphoneME MR2 API (J2ME)29865Wed May 02 18:00:06 BST 2007com.sun.midp.midlet

MIDletStateHandler

public class MIDletStateHandler extends Object
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 ACTIVE, PAUSED, and DESTROYED states.

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.

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.

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:

  • ACTIVE_PENDING - The MIDlet is still PAUSED but will be ACTIVE after startApp is called when the state is next processed.
  • PAUSE_PENDING - The MIDlet is still ACTIVE but will be PAUSED after pauseApp is called when the state is next processed.
  • DESTROY_PENDING - Indicates that the MIDlet needs to be DESTROYED. The MIDlet's destroyApp has not yet been called.
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.

When a MIDlet's state is changed to ACTIVE, PAUSED, or DESTROYED 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

Fields Summary
private MIDletSuite
midletSuite
the current MIDlet suite.
private MIDletLoader
midletLoader
loads the MIDlets from a suite's JAR in a VM specific way.
private MIDletPeer[]
midlets
array of MIDlets.
private int
nmidlets
current number of MIDlets [0..n-1].
private int
scanIndex
next index to be scanned by selectForeground.
private static MIDletStateHandler
stateHandler
The event handler of all MIDlets in an Isolate.
private static MIDletStateListener
listener
The listener for the state of all MIDlets in an Isolate.
private static Object
createMIDletLock
Serializes the creation of MIDlets.
private static MIDletPeer
newMidletPeer
New MIDlet peer waiting for the next MIDlet created to claim it.
private MIDletPeer
underConstructionPeer
MIDlet peer for MIDlet being constructed but not registered yet.
Constructors Summary
private MIDletStateHandler()
Construct a new MIDletStateHandler object.


              
      
        nmidlets = 0;

        // start with 5 empty slots, we will add more if needed
        midlets = new MIDletPeer[5];
    
Methods Summary
private voidcreateAndRegisterMIDlet(int externalAppId, java.lang.String classname)
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


        listener.midletPreStart(getMIDletSuite(), classname);
        register(createMIDlet(externalAppId, classname));
    
private javax.microedition.midlet.MIDletcreateMIDlet(int externalAppId, java.lang.String classname)
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

        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("Recusive 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 {
                    midlet = midletLoader.newInstance(getMIDletSuite(),
                                                      classname);
                    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);
                    }
                }
            } finally {
                /* Make sure the creation window is closed. */
                newMidletPeer = null;
            }
        }
    
public voiddestroySuite()
Destroys all running MIDlets in this suite only. This method is only used by the push registry in single VM mode.

        synchronized (this) {
            for (int i = 0; i < nmidlets; i++) {
                if (midlets[i].getState() != MIDletPeer.DESTROYED) {
                    midlets[i].
                        setStateWithoutNotify(MIDletPeer.DESTROY_PENDING);
                }
            }

            this.notify();
        }
    
private intfindMIDletByClass(MIDletPeer m)
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.

        // Find it in the list
        for (int i = 0; i < nmidlets; i++) {
            if (m.getMIDlet().getClass() ==
                midlets[i].getMIDlet().getClass()) {
                return i;
            }
        }
        return -1;
    
public java.lang.StringgetFirstRunningMidlet()
Gets the class name first midlet in the list of running MIDlets.

return
the classname or null if no midlet are running

        synchronized (this) {
            if (nmidlets <= 0) {
                return null;
            }

            return midlets[0].midlet.getClass().getName();
        }
    
public MIDletEventConsumergetMIDletEventConsumer(com.sun.midp.security.SecurityToken token, java.lang.String name)
Gets MIDlet event consumer of the named MIDlet.

param
token security token for authorizing the caller
param
name class name of MIDlet
return
reference of the MIDlet event consumer

        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;
    
public static intgetMIDletState(javax.microedition.midlet.MIDlet midlet)
Retrieves current state of the MIDlet given.

param
midlet the MIDlet of interest
return
the MIDlet state as defined in MIDletPeer class

        return MIDletPeer.getMIDletPeer(midlet).getState();
    
public MIDletSuitegetMIDletSuite()
Provides a object with a mechanism to retrieve MIDletSuite being run.

return
MIDletSuite being run

        return midletSuite;
    
public static synchronized com.sun.midp.midlet.MIDletStateHandlergetMidletStateHandler()
Gets the MIDletStateHandler that manages the lifecycle states of MIDlets running in an Isolate.

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.

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

        /*
         * 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;
    
public voidinitMIDletStateHandler(com.sun.midp.security.SecurityToken token, MIDletStateListener theMIDletStateListener, MIDletLoader theMidletLoader, PlatformRequest thePlatformRequestHandler)
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


        token.checkIfPermissionAllowed(Permissions.AMS);

        listener = theMIDletStateListener;
        midletLoader = theMidletLoader;

        MIDletPeer.initClass(this, listener, thePlatformRequestHandler);
    
public booleanisRunning(java.lang.String name)
Checks if the named MIDlet has already been instantiated.

param
name class name of MIDlet to test if currently run
return
true if an instance of the MIDlet is already running

        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;
    
public static MIDletPeernewMIDletPeer(com.sun.midp.security.SecurityToken token, javax.microedition.midlet.MIDlet m)
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 null.
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 createMIDlet.

        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;
        }
    
private voidregister(javax.microedition.midlet.MIDlet midlet)
Registers a MIDlet being constructed.

param
midlet to be registered with this state handler

        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();
        }
    
private MIDletPeerselectByPriority()
Looks through the current MIDlets and select one to be processed.

Note: that this method is called while synchronized on "this"

return
the MIDlet to process next

        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;
    
public voidstartMIDlet(java.lang.String classname, java.lang.String displayName)
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

        startMIDlet(0, classname, displayName);
    
public voidstartMIDlet(int externalAppId, java.lang.String classname, java.lang.String displayName)
Starts a MIDlet from outside of the package.

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


        midletSuite.checkIfPermissionAllowed(Permissions.AMS);
        createAndRegisterMIDlet(externalAppId, classname);
    
public voidstartMIDlet(com.sun.midp.security.SecurityToken token, java.lang.String classname, java.lang.String displayName)
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

        startMIDlet(token, 0, classname, displayName);
    
public voidstartMIDlet(com.sun.midp.security.SecurityToken token, int externalAppId, java.lang.String classname, java.lang.String 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


        token.checkIfPermissionAllowed(Permissions.AMS);
        createAndRegisterMIDlet(externalAppId, classname);
    
public voidstartSuite(MIDletSuiteExceptionListener exceptionHandler, MIDletSuite aMidletSuite, int externalAppId, java.lang.String classname)
Runs MIDlets until there are none. Handle any pending state transitions of any MIDlet. If there are none, wait for transitions. If there is no foreground MIDlet select one that is ACTIVE and has setCurrent != null.

If the foreground MIDlet changes from the ACTIVE_FOREGROUND state it automatically looses the foreground and and new one is selected.

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


        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());
                    break;
                }
            } catch (Throwable ex) {
                if (Logging.TRACE_ENABLED) {
                    Logging.trace(ex, "Exception in startSuite");
                }
                exceptionHandler.handleException(ex);
            }
        }
    
private voidunregister(MIDletPeer m)
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

        // 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;
            }
        }