FileDocCategorySizeDatePackage
Scheduler.javaAPI DocJ2ME MIDP 2.021288Thu Nov 07 12:02:22 GMT 2002com.sun.midp.midlet

Scheduler

public class Scheduler extends Object implements com.sun.midp.lcdui.SystemEventListener, com.sun.midp.lcdui.MIDletEventListener
The Scheduler starts and controls MIDlets through the lifecycle states. MIDlets are created using its no-arg Constructor. Once created it is sequenced through the start, paused, and destroy states.

The Scheduler is a singleton. The Scheduler is retrieved with getScheduler(). The Scheduler can be replaced by subclassing. Events that affect MIDlet scheduling should notify the Scheduler. Typical events are MIDlet creation, changing of MIDlet states, etc.

To replace the Scheduler, subclass it and then set the "com.sun.midp.midlet.scheduler" property to the name of the subclass. When the Scheduler is first retrieved, the property is checked and the instance created. If left unset the Scheduler class is used unchanged.

The MIDlet methods are protected in the javax.microedition.midlet package so the Scheduler can not call them directly. The MIDletState object and MIDletProxy subclass class allow the Scheduler 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 default implementation of the Scheduler introduces a couple of extra internally only visible states to allow processing of MIDlet requested state changes in the control thread and serialized with other state changes. The additional states are:

  • PAUSED_RESUME - Similar to PAUSED but also indicates that this MIDlet has requested to be resumed.
  • DESTROY_PENDING - Indicates that the MIDlet needs to be DESTROYED. The MIDlet's destroyApp has not yet been called.
The Scheduler 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 is started, put in ACTIVE state, its display is activated by the scheduler using the display manager. When a MIDlet is destroyed its display is deactivated. When MIDlet is paused by the MIDlet itself, its display is deactivated and it will not be restarted unless the MIDlet requests itself to be restarted. When a MIDlet is paused by a UI event from the display manager the display of the MIDlet is not deactivated but suspended, which means it can be resumed by system event such as a user selection.

It is possible that as far as the Scheduler is concerned there is no ACTIVE MIDlet. The only case in which this should arise is when all MIDlets are PAUSED or DESTROYED. From the user point of view this is quite bad. This case is the same as all off the active MIDlets not have a current displayable. So display manager will need to "default screen". Later when multiple MIDlet suites can be run, the VM will run in the background and the the application selector will run.

For the specific handling of events received from the display manager see the documenation for implemenation each of listener methods.

see
MIDlet
see
MIDletState
see
DisplayManager

Fields Summary
private MIDletSuite
midletSuite
the current MIDlet suite
private MIDletState[]
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 com.sun.midp.lcdui.DisplayManager
displayManager
display manager
private boolean
systemShutdown
Flag to indicate if the system was shutdown.
private static Object
mutex
used to wait for MIDlet state changes
private static Scheduler
scheduler
the manager of all MIDlets
private Thread
schedulerThread
the thread on which the scheduler is active
Constructors Summary
protected Scheduler()
Construct a new Scheduler object.

        /*
         * The fact that there is only one scheduler is a security feature.
         * Multiple schedulers are allow in the future, the constructor
         * should require security domain parameter and it should be
         * checked.
         */
        if (scheduler != null) {
            throw new Error("Only one scheduler instance allowed");
        }
        
        mutex = new Object();
        nmidlets = 0;

        // start with 5 empty slots, we will add more if needed
        midlets = new MIDletState[5];
    
Methods Summary
public voiddestroyMIDlet(javax.microedition.midlet.MIDlet midlet)
Destroy the MIDlet given midlet. Return to the control to AMS if there are no another active MIDlets in the scheduled. This is a system callback which allows a user to forcibly exit a running MIDlet in cases where it is necessary (such as a rogue MIDlet which does not provide exit capability).

param
midlet midlet that the event applies to

        MIDletState state = MIDletStateMap.getState(midlet);
        state.setState(MIDletState.DESTROY_PENDING);
    
private intfindMIDletByClass(MIDletState m)
Find 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 the mutex.

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;
    
private com.sun.midp.lcdui.DisplayManagergetDisplayManager()
Get the display manager.

return
the display manager

        if (displayManager == null) {
            displayManager = DisplayManagerFactory.getDisplayManager();
        }
        
        return displayManager;
    
public MIDletSuitegetMIDletSuite()
Provides a MIDletProxy with a mechanism to retrieve MIDletSuite being scheduled.

return
MIDletSuite being scheduled

        return midletSuite;
    
protected java.lang.ObjectgetMutex()
Return the mutex for synchronization and notification of changes to a MIDlets state. It is used by MIDlets when they are changing states. It is the mutex used for all state changes.

return
mutex object for synchronizing this scheduler

        return mutex;
    
public static synchronized com.sun.midp.midlet.SchedulergetScheduler()
Get the Scheduler that manages the lifecycle states MIDlets. If the instance of the Scheduler has already been created it is returned. If not it is created from the value of the "com.sun.midp.midlet.scheduler" property. If that property is null the default scheduler (this class) is instantiated. The instance becomes the Scheduler for this process

return
the MIDlet management software scheduler

        /*
         * If not scheduler has been created, create one now.
         * If the scheduler class has been overridden use it.
         */
        if (scheduler == null) {
            String prop =
                Configuration.getProperty("com.sun.midp.midlet.scheduler");
            if (prop != null) {
                Class c;
                try {
                    c =  Class.forName(prop);
                } catch (Throwable ex) {
                    throw new Error("A scheduler class cannot be loaded " +
                                    "from the value of property " +
                                    "com.sun.midp.midlet.scheduler");
                }

                try {
                    scheduler = (Scheduler)c.newInstance();
                } catch (Throwable ex) {
                    throw new Error("The construction of " + prop +
                                    " instance failed");
                }
            } else {
                /* This is the default scheduler class */
                scheduler = new Scheduler();
            }
        }

        return scheduler;
    
public booleanisDispatchThread()
Returns true if the current thread is the Scheduler's thread.

return
true if the current thread is this Scheduler's thread

        // NOTE: This will have to be removed when Display
        // changes queued in the event handler
        return (Thread.currentThread() == schedulerThread);
    
public booleanisScheduled(java.lang.String name)
Check if the named MIDlet has already been instantiated.

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

	boolean found = false;
	synchronized (mutex) {
            for (int i = 0; i < nmidlets; i++) {
                if (midlets[i].getMIDlet().getClass().getName().equals(name)) {
		    found = true;
		    break;
                }
            }
        }
	return found;
    
public voidpauseMIDlet(javax.microedition.midlet.MIDlet midlet)
Pause the current foreground MIDlet and return to the AMS or "selector" to possibly run another MIDlet in the currently active suite. Currently, the RI does not support running multiple MIDlets, but if it did, this system callback would allow it. The listener should not deactivate the display of the MIDlet.

param
midlet midlet that the event applies to

        MIDletState state = MIDletStateMap.getState(midlet);
        state.setState(MIDletState.PAUSE_PENDING);
    
private static voidprintException(java.lang.String msg, java.lang.Exception ex)
Print an exception with a reason. Should be logged instead.

param
msg string to associate with the current exception
param
ex the exception to be reported

        try {
            System.out.println(msg);
            if (ex.getMessage() == null) {
                System.out.println(ex);
            }
            ex.printStackTrace();
        } catch (Exception e) {
        }
    
protected voidregister(javax.microedition.midlet.MIDlet midlet)
Register a MIDlet being constructed.

param
midlet to be registered with this scheduler

        synchronized (mutex) {
            MIDletState state = MIDletStateMap.getState(midlet);

            /*
             * If a MIDlet of the same class is already running
             * Make the existing MIDlet current so that schedule() will run it
             */
            int i = findMIDletByClass(state);
            if (i >= 0) {
                state.setState(MIDletState.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) {
                MIDletState[] n = new MIDletState[nmidlets+5];
                System.arraycopy(midlets, 0, n, 0, nmidlets);
                midlets = n;
            }

            // Add it to the end of the list
            midlets[nmidlets++] = state;

            mutex.notify();
        }
    
public booleanschedule(MIDletSuite aMidletSuite)
Run 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
aMidletSuite the current midlet suite
return
true if a method ended because the last MIDlet was destroyed normally, false if the system is shutting down.
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 scheduled.");
        
	}

        // NOTE: This will have to be removed when Display
        // changes queued in the event handler
        schedulerThread = Thread.currentThread();

	getDisplayManager().addSystemEventListener(this);

        midletSuite = aMidletSuite;
        
        register(
           MIDletState.createMIDlet(midletSuite.getInitialMIDletClassname()));

        /*
         * Until there are no MIDlets
         * Scan all the MIDlets looking for state changes.
         */
        while (nmidlets > 0) {
            try {
                MIDletState curr;
                int state;

                synchronized (mutex) {
                    /*
                     * 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 MIDletState.ACTIVE:
                        // fall through
                    case MIDletState.PAUSED:
                        // Wait for some change in the state of a MIDlet
                        // that needs attention
                        try {
                            mutex.wait();
                        } catch (InterruptedException e) {
                        }

                        continue;

                    case MIDletState.PAUSED_RESUME:
                        // fall through
                    case MIDletState.ACTIVE_PENDING:
                        // Start the MIDlet
                        curr.setStateWithoutNotify(MIDletState.ACTIVE);
                        break;

                    case MIDletState.PAUSE_PENDING:
                        // The display manager wants the MIDlet paused
                        curr.setStateWithoutNotify(MIDletState.PAUSED);
                        break;

                    case MIDletState.DESTROY_PENDING:
                        curr.setStateWithoutNotify(MIDletState.DESTROYED);
                        break;

                    case MIDletState.DESTROYED:
                        unregister(curr);
                        break;
                        
                    default:
                        throw new Error("Illegal MIDletState state " +
                                    curr.getState());
                    }
                }

                /* perform work that may block outside of the mutex. */
                switch (state) {
                case MIDletState.PAUSED_RESUME:
                    getDisplayManager().activate(this, curr.getMIDlet());
                    
                    // fall through
                case MIDletState.ACTIVE_PENDING:
                    try {
                        curr.startApp();
                    } catch (Exception ex) {
                        printException("startApp threw an Exception", ex);
                        curr.setState(MIDletState.DESTROY_PENDING);
                    }
                    break;

                case MIDletState.PAUSE_PENDING:
                    try {
                        curr.pauseApp();
                    } catch (Exception ex) {
                        printException("pauseApp threw an Exception", ex);
                        curr.setState(MIDletState.DESTROY_PENDING);
                    }
                    
                    break;

                case MIDletState.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) {
                        // Ignore the exception
                    } catch (Exception ex) {
                        printException("destroyApp threw an Exception",
                                       ex);
                    }

                    break;

                case MIDletState.DESTROYED:
                    getDisplayManager().deactivate(curr.getMIDlet());
                    break;
                }
            } catch (Exception ex) {
                System.out.println("Exception in schedule");
                ex.printStackTrace();
            }
        }

        midletSuite.saveSettings();
        midletSuite = null;
        getDisplayManager().releaseSystemEventListener(this);

        return !systemShutdown;
    
public voidscheduleMIDlet(javax.microedition.midlet.MIDlet midlet)
Schedule a MIDlet from outside of the package.

param
midlet to be registered with this scheduler
exception
SecurityException if the suite does not have the AMS permission.

        midletSuite.checkIfPermissionAllowed(Permissions.AMS);

        register(midlet);
    
private MIDletStateselectByPriority()
Look through the current MIDlets and select one to be processed.

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

return
the MIDlet to process next

        MIDletState found = null; // Chosen MIDletState
        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 voidshutdown()
Shutdown all running MIDlets and prepare the MIDP runtime to exit completely.

        synchronized (mutex) {
            systemShutdown = true;

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

            mutex.notify();
        }
    
public voidstartMIDlet(javax.microedition.midlet.MIDlet midlet)
Start the currently suspended state. This is a result of the underlying system returning control to MIDP. Any previously paused foreground MIDlet will be restarted and the Display will be refreshed. The listener should not activate the display of the MIDlet since this will be done automatically.

param
midlet midlet that the event applies to

        MIDletState state = MIDletStateMap.getState(midlet);
        state.setState(MIDletState.ACTIVE_PENDING);
    
private voidunregister(MIDletState m)
Remove a MIDlet from the list if it is there, otherwise ignore the request. Call only while synchronized on mutex.

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