/*
 *
 *
 * Copyright  1990-2006 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.main;

import java.util.*;

import com.sun.midp.events.EventQueue;

import com.sun.midp.midlet.MIDletStateHandler;
import com.sun.midp.midlet.MIDletSuite;

import com.sun.midp.security.Permissions;
import com.sun.midp.security.SecurityToken;
import com.sun.midp.security.ImplicitlyTrustedClass;
import com.sun.midp.security.SecurityInitializer;

import com.sun.midp.suspend.SuspendSystem;
import com.sun.midp.suspend.SuspendSystemListener;

/**
 * Manages a list of MIDlet proxies, each proxy representing a running MIDlet
 * and tracks which MIDlet has the foreground display, the list only available
 * to objects running in the AMS isolate.
 * <p>
 * The list is updated upon receiving events.  See the process method for event
 * processing.
 * <p>
 * Objects can listen (see MIDletProxyListListener) to the list for additions,
 * updates to proxies, and removals as will as set foreground events and
 * select foreground events.
 * <p>
 * This class also provides a shutdown method and processes a shutdown event
 * to enable the method be used by native code.
 *
 */
public class MIDletProxyList
        implements MIDletControllerEventConsumer, SuspendSystemListener {

    /** MIDletProxy added constant. */
    static final int PROXY_ADDED = 0;

    /** MIDletProxy removed constant. */
    static final int PROXY_REMOVED = 1;

    /** The one and only MIDlet proxy list. */
    private static MIDletProxyList midletProxyList;

    /** True when the system is shutting down. */
    private static boolean shutdownFlag;

    /** What objects should get list changes. */
    private Vector listeners = new Vector(2, 2);

    /** Vector to hold MIDlet proxies. */
    private Vector midletProxies = new Vector(5, 5);

    /** The foreground MIDlet. */
    private MIDletProxy foregroundMidlet;

    /** The one and only displayController. */
    private DisplayController displayController;

    /** True if all midlets are paused, false - otherwise */
    private boolean allPaused; // = false

    /**
     * MIDlet proxy whose peer application runs in the AMS isolate. Should
     * be accessed through findAmsProxy(), may be invalid otherwise.
     */
    private MIDletProxy amsProxy;

    /**
     * Class registered in SecurityInitializer that identifies this
     * implementation permissions for accessing restricted API's
     */
    private static class SecurityTrusted implements ImplicitlyTrustedClass {}

    /** Security token for provileged access to restricted API's. */
    private static SecurityToken classSecurityToken =
        SecurityInitializer.requestToken(new SecurityTrusted());

    /**
     * Called by the MIDlet suite loader in AMS Isolate to intialize the
     * midletProxy list. Shall be called only by MIDletSuiteLoader's main().
     *
     * @param theMIDletProxyList proxy list instance to be used
     *                           as MIDlet controller container
     *
     * Should only be called in the AMS Isolate.
     */
    static void initClass(MIDletProxyList theMIDletProxyList) {

        /*
        TBD: the code below is commented until
        the issue with non-static Logging.assertTrue
        will be fixed !

        Logging.assertTrue(theMIDletProxyList != null,
                           "theMIDletProxyList must be non-null");
        Logging.assertTrue(midletProxyList == null,
                           "midletProxyList must be initialized only once");
        */
        midletProxyList = theMIDletProxyList;
    }

    /**
     * Get a reference to the MIDlet proxy list in a secure way.
     * The calling suite must have the com.sun.midp.ams permission "allowed".
     *
     * Should only be called in the AMS Isolate.
     *
     * @return MIDP MIDlet proxy list reference
     */
    public static MIDletProxyList getMIDletProxyList() {
        return getMIDletProxyList(null);
    }

    /**
     * Get a reference to the MIDlet proxy list in a secure way.
     * The calling suite must have the com.sun.midp.ams permission "allowed".
     *
     * Should only be called in the AMS Isolate.
     *
     * @param token SecurityToken with the AMS permission allowed or
     *              null to use the midletSuite permission
     *
     * @return MIDP MIDlet proxy list reference
     */
    public static MIDletProxyList getMIDletProxyList(SecurityToken token) {
        if (token != null) {
            token.checkIfPermissionAllowed(Permissions.AMS);
        } else {
            MIDletSuite midletSuite = MIDletStateHandler.
                getMidletStateHandler().getMIDletSuite();

            midletSuite.checkIfPermissionAllowed(Permissions.AMS);
        }

        return midletProxyList;
    }

    /**
     * Returns shutdown status
     *
     * @return true if shutdown is in progress, else false
     */
    static boolean shutdownInProgress() {
        return shutdownFlag;
    }

    /**
     * Package private constructor.
     * Shall be called from MIDletSuiteLoader's main()
     *
     * @param  eventQueue reference to the event queue
     */
    MIDletProxyList(EventQueue eventQueue) {

        displayController = new DisplayController(this);

        /* register event listener for events processed by MIDletProxyList */
        new MIDletControllerEventListener(eventQueue, this);
    }

    /**
     * Enables the display controller to be replaced by an application
     * manager.
     *
     * @param newController new display controller
     */
    public void setDisplayController(DisplayController newController) {
        displayController = newController;
    }

    /**
     * Add a listener for MIDlet proxy list changes.
     *
     * @param listener MIDlet proxy list listener
     */
    public void addListener(MIDletProxyListListener listener) {
        listeners.addElement(listener);
    }

    /**
     * Remove a listener for MIDlet proxy list changes.
     *
     * @param listener MIDlet proxy list listener
     */
    public void removeListener(MIDletProxyListListener listener) {
        listeners.removeElement(listener);
    }

    /**
     * Get an enumeration of MIDlet proxies.
     *
     * @return enumeration of midletProxys
     */
    public Enumeration getMIDlets() {
        Vector v = new Vector();
        synchronized (midletProxies) {
            int size = midletProxies.size();
            for (int i = 0; i < size; i++) {
                v.addElement(midletProxies.elementAt(i));
            }
       }
        return v.elements();
    }

    /**
     * Shutdown the system by asynchronously destroying all MIDlets.
     *
     */
    public void shutdown() {
        synchronized (midletProxies) {
            if (shutdownFlag) {
                return;
            }

            shutdownFlag = true;

            if (midletProxies.size() == 0) {
                /*
                 * We are done if no proxyies are in the list.
                 * Notify any objects waiting for shutdown.
                 */
                midletProxies.notifyAll();
                return;
            }

            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                MIDletProxy current = (MIDletProxy)midletProxies.elementAt(i);

                current.destroyMidlet();
            }
        }
    }

    /** Wait for the system to asynchronously destroy all MIDlets. */
    public void waitForShutdownToComplete() {
        synchronized (midletProxies) {
            // Wait for shutdown to be called.
            while (!shutdownFlag) {
                try {
                    midletProxies.wait();
                } catch (InterruptedException ie) {
                    return;
                }
            }

            // Wait for all MIDlets to be destroyed.
            while (midletProxies.size() > 0) {
                try {
                    midletProxies.wait();
                } catch (InterruptedException ie) {
                    return;
                }
            }
        }
    }

    /**
     * Find the MIDletProxy that has matching Isolate ID and Display ID.
     *
     * @param isolateId Isolate ID
     * @param displayId Display ID
     *
     * @return a reference to the matching MIDletProxy or null if no match
     */
    private MIDletProxy findMIDletProxy(int isolateId, int displayId) {
        synchronized (midletProxies) {
            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                MIDletProxy current = (MIDletProxy)midletProxies.elementAt(i);

                if (current.getIsolateId() == isolateId &&
                        current.getDisplayId() == displayId) {
                    return current;
                }
            }
        }

        return null;
    }

    /**
     * Find the MIDletProxy that has matching suiteId and classname.
     *
     * @param suiteId the suiteId of the target application
     * @param classname classname of the MIDlet
     *
     * @return a reference to the matching MIDletProxy or null if no match
     */
    public MIDletProxy findMIDletProxy(int suiteId, String classname) {
        synchronized (midletProxies) {
            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                MIDletProxy current = (MIDletProxy)midletProxies.elementAt(i);

                if (current.getSuiteId() == suiteId &&
                        current.getClassName().equals(classname)) {
                    return current;
                }
            }
        }

        return null;
    }

    /**
     * Find the MIDletProxy that has matching external app ID.
     *
     * @param externalAppId ID assigned by the external application manager
     *
     * @return a reference to the matching MIDletProxy or null if no match
     */
    public MIDletProxy findMIDletProxy(int externalAppId) {
        synchronized (midletProxies) {
            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                MIDletProxy current = (MIDletProxy)midletProxies.elementAt(i);

                if (current.getExternalAppId() == externalAppId) {
                    return current;
                }
            }
        }

        return null;
    }

    /**
     * Retireves proxy whose peer application runs in the AMS isolate.
     * The proxy is identified by isolate ID since only one application
     * can run in the AMS solate.
     * @return the proxy or null if one cannot be found
     */
    public MIDletProxy findAmsProxy() {
        if (null == amsProxy) {
            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                MIDletProxy current = (MIDletProxy)midletProxies.elementAt(i);

                if (current.getIsolateId() == MIDletSuiteUtils.getAmsIsolateId()) {
                    amsProxy = current;
                    break;
                }
            }
        }

        return amsProxy;
    }

    /**
     * Process a MIDlet created notification.
     * MIDletControllerEventConsumer I/F method.
     *
     * @param midletSuiteId ID of the MIDlet suite
     * @param midletClassName Class name of the MIDlet
     * @param midletIsolateId isolate ID of the sending MIDlet
     * @param midletExternalAppId ID of given by an external application
     *                            manager
     * @param midletDisplayName name to show the user
     */
    public void handleMIDletCreateNotifyEvent(
        int midletSuiteId,
        String midletClassName,
        int midletIsolateId,
        int midletExternalAppId,
        String midletDisplayName) {

        MIDletProxy midletProxy = findMIDletProxy(midletSuiteId,
                                                  midletClassName);
        if (midletProxy != null) {
            /**
             * The isolate this MIDlet was last run in, died because the
             * event proccessing encountered a fatal error, which ends
             * the isolate without notifing the proxy list.
             * So just remove the MIDlet's proxy now.
             * So we can add the new midlet proxy.
             */
            removeMidletProxy(midletProxy);
        }


        /* MIDlet's are constructed in PAUSED state. */
        midletProxy = new MIDletProxy(this,
            midletExternalAppId,
            midletIsolateId,
            midletSuiteId,
            midletClassName,
            midletDisplayName,
            MIDletProxy.MIDLET_PAUSED);

        midletProxies.addElement(midletProxy);

        notifyListenersOfProxyListChange(midletProxy, PROXY_ADDED);

        displayController.midletCreated(midletProxy);
    }

    /**
     * Process a MIDlet active notification
     * MIDletControllerEventConsumer I/F method.
     *
     * TBD: param midletProxy proxy with information about MIDlet
     *
     * @param midletSuiteId ID of the MIDlet suite
     * @param midletClassName Class name of the MIDlet
     */
    public void handleMIDletActiveNotifyEvent(
        int midletSuiteId,
        String midletClassName) {

        MIDletProxy midletProxy = findMIDletProxy(midletSuiteId,
                                                  midletClassName);
        if (midletProxy == null) {
            /*
             * There is nothing we can do for the other events
             * if a proxy was not found.
             *
             * Sometimes an display can send an event after a
             * MIDlet's destroyApp method is called and the proxy removed
             * from this list. One of the cases is when
             * a thread the MIDlet started and is still running after
             * the destroyApp method has returned, calls display.setCurrent
             * with null while cleaning up.
             */
            return;
        }

        midletProxy.setMidletState(MIDletProxy.MIDLET_ACTIVE);
        notifyListenersOfProxyUpdate(midletProxy,
                                     MIDletProxyListListener.MIDLET_STATE);
        setForegroundMIDlet(displayController.midletActive(midletProxy));
        notifyIfMidletActive();
    }

    /**
     * Process a MIDlet paused notification.
     * MIDletControllerEventConsumer I/F method.
     *
     * TBD: param midletProxy proxy with information about MIDlet
     *
     * @param midletSuiteId ID of the MIDlet suite
     * @param midletClassName Class name of the MIDlet
     */
    public void handleMIDletPauseNotifyEvent(
        // MIDletProxy midletProxy) {
        int midletSuiteId,
        String midletClassName) {

        MIDletProxy midletProxy = findMIDletProxy(midletSuiteId,
                                                  midletClassName);
        if (midletProxy == null) {
            /*
             * There is nothing we can do for the other events
             * if a proxy was not found. See midletActiveNotification().
             */
            return;
        }

        midletProxy.setMidletState(MIDletProxy.MIDLET_PAUSED);
        notifyListenersOfProxyUpdate(midletProxy,
                                     MIDletProxyListListener.MIDLET_STATE);

        setForegroundMIDlet(displayController.midletPaused(midletProxy));
        notifyIfAllPaused();
    }

    /**
     * Process a MIDlet destroyed event.
     * MIDletControllerEventConsumer I/F method.
     *
     * TBD: param midletProxy proxy with information about MIDlet
     *
     * @param midletSuiteId ID of the MIDlet suite
     * @param midletClassName Class name of the MIDlet
     */
    public void handleMIDletDestroyNotifyEvent(
        // MIDletProxy midletProxy) {
        int midletSuiteId,
        String midletClassName) {

        MIDletProxy midletProxy = findMIDletProxy(midletSuiteId,
                                                  midletClassName);
        if (midletProxy == null) {
            /*
             * There is nothing we can do for the event
             * if a proxy was not found. See midletActiveNotification().
             */
            return;
        }

        midletProxy.destroyedNotification();
        removeMidletProxy(midletProxy);
    }


    /**
     * Processes a MIDLET_RESUME_REQUEST event.
     *
     * MIDletControllerEventConsumer I/F method.
     *
     * @param midletSuiteId ID of the MIDlet suite
     * @param midletClassName Class name of the MIDlet
     */
    public void handleMIDletResumeRequestEvent(int midletSuiteId,
                                               String midletClassName) {
        MIDletProxy midletProxy = findMIDletProxy(midletSuiteId,
                                                  midletClassName);
        if (midletProxy == null) {
            /*
             * There is nothing we can do for the event
             * if a proxy was not found. See midletActiveNotification().
             */
            return;
        }

        // Just grant the request.
        midletProxy.activateMidlet();
    }

    /**
     * Handles notification event of MIDlet resources pause.
     * MIDletControllerEventConsumer I/F method.
     *
     * @param midletSuiteId ID of the MIDlet suite
     * @param midletClassName Class name of the MIDlet
     */
    public void handleMIDletRsPauseNotifyEvent(
        int midletSuiteId,
        String midletClassName) {

        MIDletProxy midletProxy = findMIDletProxy(midletSuiteId,
                                                  midletClassName);
        if (midletProxy == null) {
            /*
             * There is nothing we can do for the other events
             * if a proxy was not found.
             */
            return;
        }

        notifyListenersOfProxyUpdate(midletProxy,
                MIDletProxyListListener.RESOURCES_SUSPENDED);
    }

    /**
     * Process a MIDlet destroy request event.
     * MIDletControllerEventConsumer I/F method.
     *
     * TBD: param midletProxy proxy with information about MIDlet
     *
     * @param midletIsolateId isolate ID of the sending Display
     * @param midletDisplayId ID of the sending Display
     */
    public void handleMIDletDestroyRequestEvent(
        // MIDletProxy midletProxy) {
        int midletIsolateId,
        int midletDisplayId) {

        MIDletProxy midletProxy = findMIDletProxy(midletIsolateId,
                                                  midletDisplayId);
        if (midletProxy == null) {
            return;
        }

        midletProxy.destroyMidlet();
    }

    /**
     * Process an ACTIVATE_ALL_EVENT.
     * MIDletControllerEventConsumer I/F method.
     *
     */
    public void handleActivateAllEvent() {
        SuspendSystem.getInstance(classSecurityToken).resume();

        synchronized (midletProxies) {
            MIDletProxy current;

            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                current = (MIDletProxy)midletProxies.elementAt(i);

                current.activateMidlet();
            }
        }
    }


    /**
     * Process a PAUSE_ALL_EVENT.
     * MIDletControllerEventConsumer I/F method.
     */
    public void handlePauseAllEvent() {
        synchronized (midletProxies) {
            MIDletProxy current;

            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                current = (MIDletProxy)midletProxies.elementAt(i);
                SuspendSystem.getInstance(classSecurityToken).
                        addSuspendDependency(current);
                current.pauseMidlet();
            }
        }

        SuspendSystem.getInstance(classSecurityToken).suspend();

    }

    /**
     * Finalizes PAUSE_ALL_EVENT processing after timeout for
     * pausing MIDlets expires.
     */
    public void terminatePauseAll() {
        synchronized (midletProxies) {
            MIDletProxy current;

            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                current = (MIDletProxy)midletProxies.elementAt(i);

                current.terminateNotPausedMidlet();
            }

        }
    }

    /**
     * Process a SHUTDOWN_ALL_EVENT.
     * MIDletControllerEventConsumer I/F method.
     *
     * It simply calls "shutdown()". In future it shall be merged with
     * "shutdown()" and substitute it.
     */
    public void handleDestroyAllEvent() {
        shutdown();
    }

    /**
     * Processes FATAL_ERROR_NOTIFICATION.
     *
     * MIDletControllerEventConsumer I/F method.
     *
     * @param midletIsolateId isolate ID of the sending isolate
     * @param midletDisplayId ID of the sending Display
     */
    public void handleFatalErrorNotifyEvent(
        int midletIsolateId,
        int midletDisplayId) {

        removeIsolateProxies(midletIsolateId);
        AmsUtil.terminateIsolate(midletIsolateId);
    }

    /**
     * Notifies the device if an active MIDlet appeared in the system.
     * The notification is produced only once when the system runs
     * out of the state with all the MIDlets being either paused 
     * or destroyed.
     */
    private void notifyIfMidletActive() {
        MIDletProxy midletProxy;

        synchronized (midletProxies) {
            if (allPaused) {
                for (int i = midletProxies.size() - 1; i >= 0; i--) {
                    midletProxy = (MIDletProxy)midletProxies.elementAt(i);
                    if (midletProxy.getMidletState() ==
                            MIDletProxy.MIDLET_ACTIVE) {
                        allPaused = false;
                        notifyResumeAll0();
                        break;
                    }
                }
            }
        }
    }

    /**
     * Notifies the device if all MIDlets considered to be paused, that is
     * at least one MIDlet is paused and others are either paused or
     * destroyed. The notification is produced only once when the system
     * runs to that state.
     */
    private void notifyIfAllPaused() {
        boolean  allMidletsPaused = false;
        int midletState;

        synchronized (midletProxies) {
            if (!allPaused) {
                for (int i = midletProxies.size() - 1; i >= 0; i--) {
                    midletState = ((MIDletProxy) midletProxies.elementAt(i)).
                            getMidletState();

                    if (MIDletProxy.MIDLET_PAUSED == midletState) {
                        allMidletsPaused = true;
                    } else if (MIDletProxy.MIDLET_DESTROYED != midletState) {
                        allMidletsPaused = false;
                        break;
                    }
                }

                if (allMidletsPaused) {
                    allPaused = true;
                    notifySuspendAll0();
                }
            }
        }
    }

    /**
     * Process a Display created notification.
     * MIDletControllerEventConsumer I/F method.
     *
     * @param midletIsolateId isolate ID of the sending Display
     * @param midletDisplayId ID of the sending Display
     * @param midletClassName Class name of the MIDlet that owns the display
     */
    public void handleDisplayCreateNotifyEvent(
        int midletIsolateId,
        int midletDisplayId,
        String midletClassName) {

        MIDletProxy midletProxy = null;

        synchronized (midletProxies) {
            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                MIDletProxy current = (MIDletProxy)midletProxies.elementAt(i);
                if (current.getIsolateId() == midletIsolateId &&
                        current.getClassName().equals(midletClassName)) {
                    midletProxy = current;
                    break;
                }
            }
        }

        if (midletProxy == null) {
            /**
             * The isolate is create a display without a MIDlet to display
             * a fatal error loading the suite in SVM mode.
             * So just do nothing, a preempt event will follow.
             */
            return;
        }

        /* Just set the display ID of the proxy. */
        midletProxy.setDisplayId(midletDisplayId);
    }

    /**
     * Process a foreground request event.
     * MIDletControllerEventConsumer I/F method.
     *
     * TBD: param midletProxy proxy with information about MIDlet
     *
     * @param midletIsolateId isolate ID of the sending Display
     * @param midletDisplayId ID of the sending Display
     * @param isAlert true if the current displayable is an Alert
     */
    public void handleDisplayForegroundRequestEvent(
        // MIDletProxy midletProxy) {
        int midletIsolateId,
        int midletDisplayId,
        boolean isAlert) {

        MIDletProxy midletProxy = findMIDletProxy(midletIsolateId,
                                                  midletDisplayId);
        if (midletProxy == null) {
            /*
             * There is nothing we can do for the event
             * if a proxy was not found. See midletActiveNotification().
             */
            return;
        }

        if (midletProxy == foregroundMidlet) {
            // force alert waiting to false, since it can't be waiting
            midletProxy.setWantsForeground(true, false);
        } else {
            midletProxy.setWantsForeground(true, isAlert);
            setForegroundMIDlet(
                displayController.foregroundRequest(midletProxy));
        }

        /**
         * The internal calls to  notifyListenersOfProxyChange() within
         * setForegroundMIDlet() should not override the behaviour of
         * listener which is an IndicatorManager in this case. So,
         * notifyListenersOfProxyChange() should be called after
         * setForegroundMIDlet().
         */
        notifyListenersOfProxyUpdate(midletProxy,
                                     MIDletProxyListListener.WANTS_FOREGROUND);
    }

    /**
     * Process a background request event.
     * MIDletControllerEventConsumer I/F method.
     *
     * TBD: param midletProxy proxy with information about MIDlet
     *
     * @param midletIsolateId isolate ID of the sending Display
     * @param midletDisplayId ID of the sending Display
     */
    public void handleDisplayBackgroundRequestEvent(
        // MIDletProxy midletProxy) {
        int midletIsolateId,
        int midletDisplayId) {

        MIDletProxy midletProxy = findMIDletProxy(midletIsolateId,
                                                  midletDisplayId);
        if (midletProxy == null) {
            /*
             * There is nothing we can do for the event
             * if a proxy was not found.
             *
             * Sometimes an display can send an event before
             * MIDlet. One of the cases is when
             * a MIDlet calls Display.setCurrent with a new displayable in
             * its constructor which happens before the MIDlet created
             * event.
             *
             * Sometimes an display can send an event after a
             * MIDlet's destroyApp method is called and the proxy removed
             * from this list. One of the cases is when
             * a thread the MIDlet started and is still running after
             * the destroyApp method has returned, calls display.setCurrent
             * with null while cleaning up.
             */
            return;
        }

        midletProxy.setWantsForeground(false, false);
        setForegroundMIDlet(displayController.backgroundRequest(midletProxy));
        notifyListenersOfProxyUpdate(midletProxy,
                                     MIDletProxyListListener.WANTS_FOREGROUND);
    }

    /**
     * Process a "display preempt start" event.
     * <p>
     * Set the foreground to a given display if a certain display
     * has the foreground. Used to start preempting.
     *
     * MIDletControllerEventConsumer I/F method.
     *
     * @param midletIsolateId isolate ID of the sending Display
     * @param midletDisplayId ID of the sending Display
     */
    public void handleDisplayPreemptStartEvent(
        int midletIsolateId,
        int midletDisplayId) {

        MIDletProxy preempting = new MIDletProxy(this, 0,
            midletIsolateId, MIDletSuite.UNUSED_SUITE_ID,
                null, null, MIDletProxy.MIDLET_ACTIVE);
        preempting.setDisplayId(midletDisplayId);

        MIDletProxy nextForeground =
            displayController.startPreempting(preempting);

        if (nextForeground != null) {
            setForegroundMIDlet(nextForeground);
        }
    }

    /**
     * Process a "display preempt stop" event.
     * <p>
     * Set the foreground to a given display if a certain display
     * has the foreground. Used to end preempting.
     *
     * MIDletControllerEventConsumer I/F method.
     *
     * @param midletIsolateId isolate ID of the sending Display
     * @param midletDisplayId ID of the sending Display
     */
    public void handleDisplayPreemptStopEvent(
        int midletIsolateId,
        int midletDisplayId) {

        MIDletProxy nextForeground =
            displayController.endPreempting(
                midletIsolateId,
                midletDisplayId);

        if (nextForeground != null) {
            setForegroundMIDlet(nextForeground);
        }
    }

    /**
     * Process a select foreground event by putting the foreground selector
     * MIDlet in the foreground.
     *
     * MIDletControllerEventConsumer I/F method.
     *
     */
    public void handleMIDletForegroundSelectEvent(int onlyFromLaunched) {
        MIDletProxy nextForeground =
            displayController.selectForeground(onlyFromLaunched == 1);

        if (nextForeground == foregroundMidlet) {
            return;
        }

        setForegroundMIDlet(nextForeground);
    }

    /**
     * Process an event to transition the foreground from a current display
     * to a target MIDlet by ID and classname. If the source display
     * does not currently own the foreground the request is ignored.
     * If the target MIDlet is found in the active list then it it set
     * as the foreground. If not found, then it should be added as
     * the next display to get the foreground (when it asks).
     *
     * MIDletControllerEventConsumer I/F method.
     *
     * @param originMIDletSuiteId ID of MIDlet from which
     *        to take forefround ownership away,
     * @param originMIDletClassName Name of MIDlet from which
     *        to take forefround ownership away
     * @param targetMIDletSuiteId ID of MIDlet
     *        to give forefround ownership to,
     * @param targetMIDletClassName Name of MIDlet
     *        to give forefround ownership to
     */
    public void handleMIDletForegroundTransferEvent(
        int originMIDletSuiteId,
        String originMIDletClassName,
        int targetMIDletSuiteId,
        String targetMIDletClassName) {

        MIDletProxy origin = findMIDletProxy(originMIDletSuiteId,
                                             originMIDletClassName);
        if (origin == null) {
            return;
        }
        // See if the foreground can be handed to the target MIDlet
        MIDletProxy target = findMIDletProxy(targetMIDletSuiteId,
                                             targetMIDletClassName);
        if (target != null) {
            target.setWantsForeground(true, false);
            // Let the DisplayController make the UE policy choice
            setForegroundMIDlet(displayController.transferRequest(origin,
                                                                  target));

            /**
            * The internal calls to  notifyListenersOfProxyChange() within
            * setForegroundMIDlet() should not override the behaviour of a
            * listener which is an IndicatorManager in this case. So,
            * notifyListenersOfProxyChange() should be called after
            * setForegroundMIDlet().
            */
            notifyListenersOfProxyUpdate(target,
                MIDletProxyListListener.WANTS_FOREGROUND);
        }
    }

    /**
     * Processes SET_FOREGROUND_BY_NAME_REQUEST event.
     * <p>
     * Set specified MIDlet to foreground.
     *
     * @param suiteId MIDlet's suite ID
     * @param className MIDlet's class name
     */
    public void handleSetForegroundByNameRequestEvent(
        int suiteId,
        String className) {

        MIDletProxy midletProxy = findMIDletProxy(suiteId, className);
        if (midletProxy != null) {
            setForegroundMIDlet(midletProxy);
        }
    }

    /**
     * Process a MIDlet start error event.
     * Notify from last to first added to allow the listener to
     * remove itself without causing a missed notification.
     *
     * MIDletControllerEventConsumer I/F method.
     *
     * @param midletSuiteId ID of the MIDlet suite
     * @param midletClassName Class name of the MIDlet
     * @param midletExternalAppId ID of given by an external application
     *                            manager
     * @param errorCode start error code
     * @param errorDetails start error details
     */
    public void handleMIDletStartErrorEvent(
        int midletSuiteId,
        String midletClassName,
        int midletExternalAppId,
        int errorCode,
        String errorDetails) {

        for (int i = listeners.size() - 1; i >= 0; i--) {
            MIDletProxyListListener listener =
                (MIDletProxyListListener)listeners.elementAt(i);

            listener.midletStartError(midletExternalAppId, midletSuiteId,
                                      midletClassName, errorCode, errorDetails);
        }
    }

    /**
     * Sets the foreground MIDlet. If the given midletProxy is paused,
     * then it will be activated before given the foreground.
     * the internal system property "pause_app_in_background" is true,
     * then previous foreground MIDlet will be paused.
     * <p>
     * The follow steps are performed when changed:<p>
     * 1. Send an event to notify the old foreground Display it has lost the
     *    foreground<p>
     * 2. Change the foreground field in this object and in the native
     *    code. <p>
     * 3. Send an event to notify the new foreground Display is has gained the
     *    foreground<p>
     *
     * @param newForeground Proxy of the MIDlet to be put in the foreground
     */
    public void setForegroundMIDlet(MIDletProxy newForeground) {
        if (newForeground != null &&
            (newForeground.getMidletState() == MIDletProxy.MIDLET_DESTROYED ||
            newForeground == foregroundMidlet ||
            newForeground.noDisplay())) {
            return;
        }

        if (foregroundMidlet != null &&
            (foregroundMidlet.getMidletState() !=
            MIDletProxy.MIDLET_DESTROYED)) {
	    /*
	     * Background MIDlet will run with a lower priority
	     */
            MIDletProxyUtils.minPriority(foregroundMidlet);
            foregroundMidlet.notifyMIDletHasForeground(false);
        }

        foregroundMidlet =
            displayController.foregroundMidletChanging(newForeground);

        /*
         * When there are no more midletProxys the foreground midletProxy
         * will be null again.
         */

        if (foregroundMidlet != null) {
            setForegroundInNativeState(foregroundMidlet.getIsolateId(),
                                       foregroundMidlet.getDisplayId());
            // This call with a true parameter will set the alertWaiting field.
            foregroundMidlet.notifyMIDletHasForeground(true);

	    /*
	     * Foreground MIDlet will run with a normal priority
	     */
            MIDletProxyUtils.normalPriority(foregroundMidlet);

            notifyListenersOfProxyUpdate(foregroundMidlet,
                MIDletProxyListListener.ALERT_WAITING);
        } else {
            setForegroundInNativeState(MIDletSuiteUtils.getAmsIsolateId(),
                                       -1);
        }
    }

    /**
     * Get the foreground MIDlet.
     *
     * @return proxy to the MIDlet that is in the foreground
     */
    public MIDletProxy getForegroundMIDlet() {
        return foregroundMidlet;
    }

    /**
     * Return true if home indicator needs to be turned on
     *
     * @return true if any MIDlet has set an Alert as the current displayable
     *   while in the background, otherwise false
     *
     */
    public boolean isAlertWaitingInBackground() {
        synchronized (midletProxies) {
            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                MIDletProxy current = (MIDletProxy)midletProxies.elementAt(i);

                if (current.isAlertWaiting()) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Check to see if the MIDlet is already started.
     *
     * @param id Suite ID of the MIDlet
     * @param className Class name of the MIDlet
     *
     * @return true if the MIDlet has been started
     */
    public boolean isMidletInList(int id, String className) {
        synchronized (midletProxies) {
            for (int i = midletProxies.size() - 1; i >= 0; i--) {
                MIDletProxy current = (MIDletProxy)midletProxies.elementAt(i);

                if (current.getSuiteId() == id &&
                    current.getClassName().equals(className)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Notify the listeners of change in the size of the proxy list.
     *
     * Notify from last to first added to allow the listener to
     * remove itself without causing a missed notification.
     *
     * @param midletProxy midletProxy that was added or removed in the list
     * @param notificationType type of change, added or removed
     */
    void notifyListenersOfProxyListChange(MIDletProxy midletProxy,
                                          int notificationType) {

        for (int i = listeners.size() - 1; i >= 0; i--) {
            MIDletProxyListListener listener =
                (MIDletProxyListListener)listeners.elementAt(i);

            switch (notificationType) {
            case PROXY_ADDED:
                listener.midletAdded(midletProxy);
                break;

            case PROXY_REMOVED:
                listener.midletRemoved(midletProxy);
                break;
            }
        }
    }

    /**
     * Notify the listeners of the midletProxy list that a proxy
     * has been updated.
     *
     * Notify from last to first added to allow the listener to
     * remove itself without causing a missed notification.
     *
     * @param midletProxy midletProxy that changed in the list
     * @param reason reason for the change
     */
    void notifyListenersOfProxyUpdate(MIDletProxy midletProxy,
                                      int reason) {

        for (int i = listeners.size() - 1; i >= 0; i--) {
            MIDletProxyListListener listener =
                (MIDletProxyListListener)listeners.elementAt(i);

            listener.midletUpdated(midletProxy, reason);
        }
    }

    /**
     * Removes a MidletProxy from the MidletProxyList
     *
     * @param midletProxy the MIDletProxy to be removed.
     */
    void removeMidletProxy(MIDletProxy midletProxy) {
        MIDletProxy preempting;

        /*
         * Remove the proxy before notifying the display controller,
         * so that new foreground search will not find MIDlet, but
         * don't notify the listener until after new foreground, since
         * the old foreground will generate a update notification.
         */
        midletProxies.removeElement(midletProxy);

        preempting = midletProxy.getPreemptingDisplay();
        if (preempting != null) {
            setForegroundMIDlet(displayController.midletDestroyed(preempting));
        } else {
            setForegroundMIDlet(
                displayController.midletDestroyed(midletProxy));
        }

        notifyListenersOfProxyListChange(midletProxy, PROXY_REMOVED);

        // An object may be waiting for the shutdown to complete
        if (shutdownFlag) {
            synchronized (midletProxies) {
                if (midletProxies.size() == 0) {
                    midletProxies.notifyAll();
                }
            }
        }
    }

    /**
     * Removes the MIDletproxies that belong to the isolate id
     *
     * @param id IsolateId
     */
    void removeIsolateProxies(int id) {
        // We are removing multiple items from the list we
        // have to remove from the enumeration.
        Enumeration e = getMIDlets();
        while (e.hasMoreElements()) {
            MIDletProxy mp = (MIDletProxy)e.nextElement();
            if (mp.getIsolateId() == id) {
                removeMidletProxy(mp);
            }
        }
    }

    /**
     * Called to notify that java side of MIDP system has been suspended.
     */
    public void midpSuspended() {
        notifyIfAllPaused();
    }

    /**
     * Called to notify that java side of MIDP system has been resumed.
     */
    public void midpResumed() {}

    /**
     * Set foreground midletProxy in the native midletProxy list state.
     *
     * @param isolateId Isolate ID
     * @param displayId Display ID
     */
    private native void setForegroundInNativeState(int isolateId,
                                                   int displayId);

    /**
     * Notify native code that all MIDlets have been paused.
     */
    private native void notifySuspendAll0();

    /**
     * Notify native code that an active MIDlet appeared in the system.
     */
    private native void notifyResumeAll0();
}
