/*
 *
 *
 * 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.appmanager;

import javax.microedition.lcdui.*;

import com.sun.midp.configurator.Constants;

import com.sun.midp.installer.*;
import com.sun.midp.main.*;
import com.sun.midp.midletsuite.*;
import com.sun.midp.midlet.MIDletSuite;
import com.sun.midp.io.j2me.push.PushRegistryInternal;

import com.sun.midp.i18n.Resource;
import com.sun.midp.i18n.ResourceConstants;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

import com.sun.midp.payment.PAPICleanUp;

import java.io.*;
import javax.microedition.rms.*;
import java.util.*;

/**
 * The Graphical MIDlet selector Screen.
 * <p>
 * It displays a list (or grid to be exact) of currently installed
 * MIDlets/MIDlet suites (including the Installer MIDlet). Each MIDlet or
 * MIDlet suite is represented by an icon with a name under it.
 * An icon from a jad file for the MIDlet/MIDlet suite representation
 * is used if possible, otherwise a default icon is used.
 *
 * There is a a set of commands per MIDlet/MIDlet suite. Note that
 * the set of commands can change depending on the corresponding MIDlet state.
 * For MIDlets/MIDlet suites that are not running the following commands are
 * available:
 * <ul>
 * <li><b>Launch</b>: Launch the MIDlet or the MIDlet Selector
 *      if it is a suite.
 * <li><b>Remove</b>: Remove the MIDlet/MIDlet suite teh user selected
 *      (with confirmation). </li>
 * <li><b>Update</b>: Update the MIDlet/MIDlet suite the user selected.</li>
 * <li><b>Info</b>: Show the user general information
 *    of the selected MIDlet/MIdlet suite. </li>
 * <li><b>Settings</b>: Let the user change the manager's settings.
 * </ul>
 *
 * For MIDlets/MIDlet suites that are running the following commands are
 * available:
 * <ul>
 * <li><b>Bring to foreground</b>: Bring the running MIDlet to foreground
 * <li><b>End</b>: Terminate the running MIDlet
 * <li><b>Remove</b>: Remove the MIDlet/MIDlet suite teh user selected
 *      (with confirmation). </li>
 * <li><b>Update</b>: Update the MIDlet/MIDlet suite the user selected.</li>
 * <li><b>Info</b>: Show the user general information
 *    of the selected MIDlet/MIdlet suite. </li>
 * <li><b>Settings</b>: Let the user change the manager's settings.
 * </ul>
 *
 * Exactly one MIDlet from a MIDlet suite could be run at the same time.
 * Each MIDlet/MIDlet suite representation corresponds to an instance of
 * MidletCustomItem which in turn maintains a reference to a MIDletSuiteInfo
 * object (that contains info about this MIDlet/MIDlet suite).
 * When a MIDlet is launched or a MIDlet form a MIDlet suite is launched
 * the proxy instance in the corresponding MidletCustomItem is set to
 * a running MIDletProxy value. It is set back to null when MIDlet exits.
 *
 * Running midlets can be distinguished from non-running MIdlets/MIDlet suites
 * by the color of their name.
 */
class AppManagerUI extends Form
    implements ItemCommandListener, CommandListener {

    /** Constant for the discovery application class name. */
    private static final String DISCOVERY_APP =
        "com.sun.midp.installer.DiscoveryApp";

    /** Constant for the certificate manager class name */
    private static final String CA_MANAGER =
        "com.sun.midp.appmanager.CaManager";

    /** Constant for the graphical installer class name. */
    private static final String INSTALLER =
        "com.sun.midp.installer.GraphicalInstaller";

    /** Constant for the graphical installer class name. */
    private static final String SUITE_SELECTOR =
        "com.sun.midp.midletsuite.Selector";

    /**
     * The font used to paint midlet names in the AppSelector.
     * Inner class cannot have static variables thus it has to be here.
     */
    private static final Font ICON_FONT = Font.getFont(Font.FACE_SYSTEM,
                                                         Font.STYLE_BOLD,
                                                         Font.SIZE_SMALL);

    /**
     * The image used to draw background for the midlet representation.
     * IMPL NOTE: it is assumed that background image is larger or equal
     * than all other images that are painted over it
     */
    private static final Image ICON_BG =
        GraphicalInstaller.getImageFromInternalStorage("_ch_hilight_bg");

    /**
     * Cashed background image width.
     */
    private static final int bgIconW = ICON_BG.getWidth();

    /**
     * Cashed background image height.
     */
    private static final int bgIconH = ICON_BG.getHeight();

    /**
     * The icon used to display that user attention is requested
     * and that midlet needs to brought into foreground.
     */
    private static final Image FG_REQUESTED =
        GraphicalInstaller.getImageFromInternalStorage("_ch_fg_requested");

    /**
     * The image used to draw disable midlet representation.
     */
    private static final Image DISABLED_IMAGE =
        GraphicalInstaller.getImageFromInternalStorage("_ch_disabled");

    /**
     * The color used to draw midlet name
     * for the hilighted non-running running midlet representation.
     */
    private static final int ICON_HL_TEXT = 0x000B2876;

    /**
     * The color used to draw the shadow of the midlet name
     * for the non hilighted non-running midlet representation.
     */
    private static final int ICON_TEXT = 0x003177E2;

    /**
     * The color used to draw the midlet name
     * for the non hilighted running midlet representation.
     */
    private static final int ICON_RUNNING_TEXT = 0xbb0000;

    /**
     * The color used to draw the midlet name
     * for the hilighted running midlet representation.
     */
    private static final int ICON_RUNNING_HL_TEXT = 0xff0000;

    /**
     * Tha pad between custom item's icon and text
     */
    private static final int ITEM_PAD = 2;

    /**
     * Cashed truncation mark
     */
    private static final char truncationMark =
        Resource.getString(ResourceConstants.TRUNCATION_MARK).charAt(0);


    /** Command object for "Exit" command for splash screen. */
    private Command exitCmd =
        new Command(Resource.getString(ResourceConstants.EXIT),
                    Command.BACK, 1);

    /** Command object for "Launch" install app. */
    private Command launchInstallCmd =
        new Command(Resource.getString(ResourceConstants.LAUNCH),
                    Command.ITEM, 1);

    /** Command object for "Launch" CA manager app. */
    private Command launchCaManagerCmd =
        new Command(Resource.getString(ResourceConstants.LAUNCH),
                    Command.ITEM, 1);

    /** Command object for "Launch". */
    private Command launchCmd =
        new Command(Resource.getString(ResourceConstants.LAUNCH),
                    Command.ITEM, 1);
    /** Command object for "Info". */
    private Command infoCmd =
        new Command(Resource.getString(ResourceConstants.INFO),
                    Command.ITEM, 2);
    /** Command object for "Remove". */
    private Command removeCmd =
        new Command(Resource.getString(ResourceConstants.REMOVE),
                    Command.ITEM, 3);
    /** Command object for "Update". */
    private Command updateCmd =
        new Command(Resource.getString(ResourceConstants.UPDATE),
                    Command.ITEM, 4);
    /** Command object for "Application settings". */
    private Command appSettingsCmd =
        new Command(Resource.
                    getString(ResourceConstants.APPLICATION_SETTINGS),
                    Command.ITEM, 5);


    /** Command object for "Cancel" command for the remove form. */
    private Command cancelCmd =
        new Command(Resource.getString(ResourceConstants.CANCEL),
                    Command.CANCEL, 1);
    /** Command object for "Remove" command for the remove form. */
    private Command removeOkCmd =
        new Command(Resource.getString(ResourceConstants.REMOVE),
                    Command.SCREEN, 1);

    /** Command object for "Back" command for back to the AppSelector. */
    Command backCmd =
        new Command(Resource.getString(ResourceConstants.BACK),
                    Command.BACK, 1);



    /** Command object for "Bring to foreground". */
    private Command fgCmd = new Command(Resource.getString
                                        (ResourceConstants.FOREGROUND),
                                        Command.ITEM, 1);

    /** Command object for "End" midlet. */
    private Command endCmd = new Command(Resource.getString
                                         (ResourceConstants.END),
                                         Command.ITEM, 1);

    /** Command object for "Yes" command. */
    private Command runYesCmd = new Command(Resource.getString
                                            (ResourceConstants.YES),
                                            Command.OK, 1);

    /** Command object for "No" command. */
    private Command runNoCmd = new Command(Resource.getString
                                           (ResourceConstants.NO),
                                           Command.BACK, 1);

    /** Display for the Manager MIDlet. */
    ApplicationManager manager;

    /** MIDlet Suite storage object. */
    private MIDletSuiteStorage midletSuiteStorage;

    /** Display for the Manager MIDlet. */
    Display display; // = null

    /** Keeps track of when the display last changed, in milliseconds. */
    private long lastDisplayChange;

    /** MIDlet to be removed after confirmation screen was accepted */
    private RunningMIDletSuiteInfo removeMsi;

    /** last Item that was selected */
    private RunningMIDletSuiteInfo lastSelectedMsi;

    /**
     * There are several Application Manager
     * midlets from the same "internal" midlet suite
     * that should not be running in the background.
     * appManagerMidlet helps to destroy them
     * (see MidletCustomItem.showNotify).
     */
    private MIDletProxy appManagerMidlet;

    /** UI used to display error messages. */
    private DisplayError displayError;

    /** True, if the CA manager is included. */
    private boolean caManagerIncluded;

    private MIDletSwitcher midletSwitcher;

    /**
     * Creates and populates the Application Selector Screen.
     * @param manager - The application manager that invoked it
     * @param displayError - The UI used to display error messages
     * @param display - The display instance associated with the manager
     * @param first - true if this is the first time AppSelector is being
     *                shown
     * @param ms - MidletSuiteInfo that should be selected. For the internal
     *             suites midletToRun should be set, for the other suites
     *             suiteId is enough to find the corresponding item.
     */
    AppManagerUI(ApplicationManager manager, Display display,
                 DisplayError displayError, boolean first,
                 MIDletSuiteInfo ms) {
        super(null);

        try {
            caManagerIncluded = Class.forName(CA_MANAGER) != null;
        } catch (ClassNotFoundException e) {
            // keep caManagerIncluded false
        }

        this.manager = manager;
        this.display = display;
        this.displayError = displayError;

        midletSwitcher = new MIDletSwitcher(this, manager, display);

        midletSuiteStorage = MIDletSuiteStorage.getMIDletSuiteStorage();

        setTitle(Resource.getString(
                ResourceConstants.AMS_MGR_TITLE));
        updateContent();

        addCommand(exitCmd);
        setCommandListener(this);

        if (first) {
            display.setCurrent(new SplashScreen(display, this));
        } else {
            // if a MIDlet was just installed
            // getLastInstalledMidletItem() will return MidletCustomItem
            // corresponding to this suite, then we have to prompt
            // the user if he want to launch a midlet from the suite.
            MidletCustomItem mci = getLastInstalledMidletItem();
            if (mci != null) {
                askUserIfLaunchMidlet();
            } else {
                display.setCurrent(this);
                if (ms != null) {
                    // Find item to select
                    if (ms.suiteId == MIDletSuite.INTERNAL_SUITE_ID) {
                        for (int i = 0; i < size(); i++) {
                            MidletCustomItem mi = (MidletCustomItem)get(i);
                            if ((mi.msi.suiteId == MIDletSuite.INTERNAL_SUITE_ID)
                                && (mi.msi.midletToRun.equals(ms.midletToRun))) {
                                display.setCurrentItem(mi);
                                break;
                            }
                        }
                    } else {
                        for (int i = 0; i < size(); i++) {
                            MidletCustomItem mi = (MidletCustomItem)get(i);
                            if (mi.msi.suiteId == ms.suiteId) {
                                display.setCurrentItem(mi);
                                break;
                            }
                        }
                    }
                } // ms != null
            }
        }
    }

    /**
     * Called when midlet selector needed.
     *
     * @param onlyFromLaunchedList true if midlet should
     *        be selected from the list of already launched midlets,
     *        if false then possibility to launch midlet is needed.
     */
    public void showMidletSwitcher(boolean onlyFromLaunchedList) {
        if (onlyFromLaunchedList && midletSwitcher.hasItems()) {
            display.setCurrent(midletSwitcher);
        } else {
            display.setCurrent(this);
        }
    }

    /**
     * Called to determine MidletSuiteInfo of the last selected Item.
     *
     * @return last selected MidletSuiteInfo
     */
    public RunningMIDletSuiteInfo getSelectedMIDletSuiteInfo() {
        return lastSelectedMsi;
    }

    /**
     * Respond to a command issued on any Screen.
     *
     * @param c command activated by the user
     * @param s the Displayable the command was on.
     */
    public void commandAction(Command c, Displayable s) {

        if (c == exitCmd) {
            if (s == this) {
                manager.shutDown();
            }
            return;
        }

        // for the rest of the commands
        // we will have to request AppSelector to be displayed
        if (c == removeOkCmd) {

            // suite to remove was set in confirmRemove()
            try {
                remove(removeMsi);
            } catch (Throwable t) {
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING, LogChannels.LC_AMS,
                                   "Throwable in removeSuitee");
                }
            }
            return;

        } else if (c == cancelCmd) {

            // null out removeMsi in remove confirmation screen
            removeMsi = null;

        } else if (c == runYesCmd) {

            // user decided run the midlet suite after installation
            MidletCustomItem mciToRun = getLastInstalledMidletItem();
            if (mciToRun != null) {
                display.setCurrentItem(mciToRun);
                launchMidlet(mciToRun.msi);
                return;
            }

        } else if (c == runNoCmd) {

            /*
             * user decided not to run the newly installed midlet suite
             *
             * if a MIDlet was just installed
             * displayLastInstalledMidlet() will return true and
             * make "this" visible with
             * the right MIDlet icon hilighted.
             */
            if (displayLastInstalledMidlet()) {
                // Last installed midlet was set as the current item
                return;
            }

        } else if (c != backCmd) {
            return;
        }

        // for back we just need to display AppSelector
        display.setCurrent(this);
    }

    /**
     * Respond to a command issued on an Item in AppSelector
     *
     * @param c command activated by the user
     * @param item the Item the command was on.
     */
    public void commandAction(Command c, Item item) {
        RunningMIDletSuiteInfo msi = ((MidletCustomItem)item).msi;
        if (msi == null) {
            return;
        }

        if (c == launchInstallCmd) {

            manager.installSuite();

        } else if (c == launchCaManagerCmd) {

            manager.launchCaManager();

        } else if (c == launchCmd) {

            launchMidlet(msi);

        } else if (c == infoCmd) {

            try {
                AppInfo appInfo = new AppInfo(msi.suiteId);
                appInfo.addCommand(backCmd);
                appInfo.setCommandListener(this);
                display.setCurrent(appInfo);
            } catch (Throwable t) {
                displayError.showErrorAlert(msi.displayName, t, null, null);
            }

        } else if (c == removeCmd) {

            confirmRemove(msi);

        } else if (c == updateCmd) {

            manager.updateSuite(msi);
            display.setCurrent(this);

        } else if (c == appSettingsCmd) {

            try {
                AppSettings appSettings = new AppSettings(msi.suiteId, display,
                                                          displayError, this);
                display.setCurrent(appSettings);

            } catch (Throwable t) {
                displayError.showErrorAlert(msi.displayName, t, null, null);
            }

        } else if (c == fgCmd) {

            manager.moveToForeground(msi);
            display.setCurrent(this);

        } else if (c == endCmd) {
            manager.exitMidlet(msi);
            display.setCurrent(this);

        }
    }

    /**
     * Called when a new midlet was launched.
     *
     * @param midlet proxy of a newly added MIDlet
     */
    void notifyMidletStarted(MIDletProxy midlet) {
        String midletClassName = midlet.getClassName();

        if (midletClassName.equals(manager.getClass().getName())) {
            return;
        }

        if (midlet.getSuiteId() == MIDletSuite.INTERNAL_SUITE_ID &&
                !midletClassName.equals(DISCOVERY_APP) &&
                !midletClassName.equals(INSTALLER) &&
                !midletClassName.equals(CA_MANAGER)) {
            appManagerMidlet = midlet;
        } else {
            MidletCustomItem ci;
            for (int i = 0; i < size(); i++) {
                ci = (MidletCustomItem)get(i);

                if (ci.msi.equals(midlet)) {
                    ci.removeCommand(launchCmd);
                    ci.removeCommand(launchInstallCmd);

                    if (caManagerIncluded) {
                        ci.removeCommand(launchCaManagerCmd);
                    }

                    ci.setDefaultCommand(fgCmd);
                    ci.addCommand(endCmd);
                    if (ci.msi.proxy == null) {
                        // add item to midlet switcher
                        midletSwitcher.append(ci.msi);
                    }
                    ci.msi.proxy = midlet;
                    return;
                }
            }
        }
    }

    /**
     * Called when state of a running midlet was changed.
     *
     * @param midlet proxy of a newly added MIDlet
     */
    void notifyMidletStateChanged(MIDletProxy midlet) {
        MidletCustomItem mci = null;

        for (int i = 0; i < size(); i++) {
            mci = (MidletCustomItem)get(i);
            if (mci.msi.proxy == midlet) {
                mci.update();
            }
        }
    }

    /**
     * Called when a running midlet exited.
     *
     * @param midlet proxy of a newly added MIDlet
     */
    void notifyMidletExited(MIDletProxy midlet) {
        String midletClassName = midlet.getClassName();

        if (midlet.getSuiteId() == MIDletSuite.INTERNAL_SUITE_ID &&
                !midletClassName.equals(DISCOVERY_APP) &&
                !midletClassName.equals(INSTALLER) &&
                !midletClassName.equals(CA_MANAGER)) {
            appManagerMidlet = null;
        } else {
            MidletCustomItem ci;

            for (int i = 0; i < size(); i++) {
                ci = (MidletCustomItem)get(i);

                if (ci.msi.equals(midlet)) {
                    ci.removeCommand(fgCmd);
                    ci.removeCommand(endCmd);

                    if (ci.msi.midletToRun != null &&
                            ci.msi.midletToRun.equals(DISCOVERY_APP)) {
                        ci.setDefaultCommand(launchInstallCmd);
                    } else if (caManagerIncluded &&
                            ci.msi.midletToRun != null &&
                            ci.msi.midletToRun.equals(CA_MANAGER)) {
                        ci.setDefaultCommand(launchCaManagerCmd);
                    } else {
                        if (ci.msi.enabled) {
                            ci.setDefaultCommand(launchCmd);
                        }
                    }

                    midletSwitcher.remove(ci.msi);
                    ci.msi.proxy = null;

                    if (removeMsi != null && removeMsi.equals(midlet)) {
                        remove(removeMsi);
                    }

                    /*
                     * When the Installer midlet quites
                     * (it is removed from the running apps list)
                     * this is a good time to see if any new MIDlet suites
                     * where added
                     * Also the CA manager could have disabled a MIDlet.
                     */
                    if (INSTALLER.equals(midletClassName)) {
                        updateContent();
                        /*
                        * After a MIDlet suite is successfully installed on the
                        * device, ask the user whether or not to launch
                        * a MIDlet from the suite.
                        */
                        MidletCustomItem mci = getLastInstalledMidletItem();
                        if (mci != null) {
                            askUserIfLaunchMidlet();
                            return;
                        }
                    } else {
                        if (CA_MANAGER.equals(midletClassName)) {
                            updateContent();
                        }
                        ci.update();
                    }

                    return;
                }
            }
        }

        // Midlet quited; display the application Selector
        display.setCurrent(this);
    }

    /**
     * Called when a midlet could not be launched.
     *
     * @param suiteId suite ID of the MIDlet
     * @param className class name of the MIDlet
     * @param errorCode error code
     * @param errorDetails error code details
     */
    void notifyMidletStartError(int suiteId, String className, int errorCode,
                                String errorDetails) {
        Alert a;
        String errorMsg;

        switch (errorCode) {
        case Constants.MIDLET_SUITE_NOT_FOUND:
            errorMsg = Resource.getString(
                ResourceConstants.AMS_MIDLETSUITELDR_MIDLETSUITE_NOTFOUND);
            break;

        case Constants.MIDLET_CLASS_NOT_FOUND:
            errorMsg = Resource.getString(
              ResourceConstants.AMS_MIDLETSUITELDR_CANT_LAUNCH_MISSING_CLASS);
            break;

        case Constants.MIDLET_INSTANTIATION_EXCEPTION:
            errorMsg = Resource.getString(
              ResourceConstants.AMS_MIDLETSUITELDR_CANT_LAUNCH_ILL_OPERATION);
            break;

        case Constants.MIDLET_ILLEGAL_ACCESS_EXCEPTION:
            errorMsg = Resource.getString(
              ResourceConstants.AMS_MIDLETSUITELDR_CANT_LAUNCH_ILL_OPERATION);
            break;

        case Constants.MIDLET_OUT_OF_MEM_ERROR:
            errorMsg = Resource.getString(
                ResourceConstants.AMS_MIDLETSUITELDR_QUIT_OUT_OF_MEMORY);
            break;

        case Constants.MIDLET_RESOURCE_LIMIT:
        case Constants.MIDLET_ISOLATE_RESOURCE_LIMIT:
            errorMsg = Resource.getString(
                ResourceConstants.AMS_MIDLETSUITELDR_RESOURCE_LIMIT_ERROR);
            break;

        case Constants.MIDLET_ISOLATE_CONSTRUCTOR_FAILED:
            errorMsg = Resource.getString(
                ResourceConstants.AMS_MIDLETSUITELDR_CANT_EXE_NEXT_MIDLET);
            break;

        case Constants.MIDLET_SUITE_DISABLED:
            errorMsg = Resource.getString(
                ResourceConstants.AMS_MIDLETSUITELDR_MIDLETSUITE_DISABLED);
            break;

        case Constants.MIDLET_INSTALLER_RUNNING:
            String[] values = new String[1];
            values[0] = className;
            errorMsg = Resource.getString(
                           ResourceConstants.AMS_MGR_UPDATE_IS_RUNNING,
                           values);
            break;

        default:
            errorMsg = Resource.getString(
                ResourceConstants.AMS_MIDLETSUITELDR_UNEXPECTEDLY_QUIT);
        }

        if (errorDetails != null) {
             errorMsg += "\n\n" + errorDetails;
        }

        displayError.showErrorAlert(null, null,
                                    Resource.getString
                                    (ResourceConstants.EXCEPTION),
                                    errorMsg);
    }

    // ------------------------------------------------------------------

    /**
     * Read in and create a MIDletInfo for newly added MIDlet suite and
     * check enabled state of currently added MIDlet suites.
     */
    private void updateContent() {
        int[] suiteIds;
        RunningMIDletSuiteInfo msi = null;
        boolean newlyAdded;

        suiteIds = midletSuiteStorage.getListOfSuites();

        // Add the Installer as the first installed midlet
        if (size() > 0) {
            msi = ((MidletCustomItem)get(0)).msi;
        }

        if (msi == null || msi.midletToRun == null ||
            !msi.midletToRun.equals(DISCOVERY_APP)) {

            msi = new RunningMIDletSuiteInfo(MIDletSuite.INTERNAL_SUITE_ID,
                DISCOVERY_APP,
                Resource.getString(ResourceConstants.INSTALL_APPLICATION),
                true) {
                    public boolean equals(MIDletProxy midlet) {
                        if (super.equals(midlet)) {
                            return true;
                        }

                        // there is one exception when 2 midlets belong to the
                        // same icon: Discovery app & Graphical installer.
                        // Graphical Installer can be launched by Discover app
                        // or when MIdlet update is needed.
                        // In such cases we simply need to set the proxy on
                        // corresponding icon (MidletCustomItem).
                        // Note that when Discovery app exits and
                        // Installer is launched
                        // notifyMidletExited() will not find corresponding
                        // icon in the list of MidletCustomItems.
                        // (that midlet exit will be ignored).
                        return (INSTALLER.equals(midlet.getClassName()));
                    }
                };

            append(msi);
        }

        if (caManagerIncluded) {
            // Add the CA manager as the second installed midlet
            if (size() > 1) {
                msi = ((MidletCustomItem)get(1)).msi;
            }

            if (msi == null || msi.midletToRun == null ||
                !msi.midletToRun.equals(CA_MANAGER)) {
                msi = new RunningMIDletSuiteInfo(MIDletSuite.INTERNAL_SUITE_ID,
                  CA_MANAGER,
                  Resource.getString(ResourceConstants.CA_MANAGER_APP), true);
                append(msi);
            }
        }

        // Add the rest of the installed midlets
        for (int lowest, i = 0; i < suiteIds.length; i++) {

            lowest = i;

            for (int k = i + 1; k < suiteIds.length; k++) {
                if (suiteIds[k] < suiteIds[lowest]) {
                    lowest = k;
                }
            }

            try {
                MIDletSuiteInfo temp =
                    midletSuiteStorage.getMIDletSuiteInfo(suiteIds[lowest]);

                RunningMIDletSuiteInfo suiteInfo =
                    new RunningMIDletSuiteInfo(temp, midletSuiteStorage);

                newlyAdded = true;
                for (int k = 0; k < size(); k++) {
                    MidletCustomItem mci = (MidletCustomItem)get(k);

                    if (suiteIds[lowest] == mci.msi.suiteId) {
                        newlyAdded = false;
                        boolean isEnabled = suiteInfo.enabled;

                        if (mci.msi.enabled != isEnabled) {
                            mci.msi.enabled = isEnabled;

                            // MIDlet suite being enabled
                            if (isEnabled) {
                                mci.setDefaultCommand(launchCmd);
                            } else { // MIDlet suite is being disabled

                                if (mci.msi.proxy == null) { // Not running
                                    mci.removeCommand(launchCmd);

                                }

                                // running MIDlets will continue to run
                                // even when disabled
                            }
                        }

                        // Update all information about the suite;
                        // if the suite's icon was changed, reload it.
                        String oldIconName = mci.msi.iconName;
                        int oldNumberOfMidlets = mci.msi.numberOfMidlets;
                        MIDletProxy oldProxy = mci.msi.proxy;

                        mci.msi = suiteInfo;
                        mci.msi.proxy = oldProxy;

                        if ((suiteInfo.iconName != null &&
                                !suiteInfo.iconName.equals(oldIconName)) ||
                            (suiteInfo.iconName == null &&
                                suiteInfo.numberOfMidlets != oldNumberOfMidlets)
                        ) {
                            mci.msi.icon = null;
                            mci.msi.loadIcon(midletSuiteStorage);
                            mci.icon = mci.msi.icon;
                        }

                        break;
                    }
                }

                if (newlyAdded) {
                    append(suiteInfo);
                }

            } catch (Exception e) {
                // move on to the next suite
            }

            suiteIds[lowest] = suiteIds[i];
        }
    }

    /**
     * Appends a MidletCustomItem to the App Selector Screen
     *
     * @param suiteInfo the midlet suite info
     *                  of the recently started midlet
     */
    private void append(RunningMIDletSuiteInfo suiteInfo) {

        MidletCustomItem ci = new MidletCustomItem(suiteInfo);

        if (suiteInfo.midletToRun != null &&
            suiteInfo.midletToRun.equals(DISCOVERY_APP)) {
            // setDefaultCommand will add default command first
            ci.setDefaultCommand(launchInstallCmd);
        } else if (caManagerIncluded && suiteInfo.midletToRun != null &&
            suiteInfo.midletToRun.equals(CA_MANAGER)) {
            // setDefaultCommand will add default command first
            ci.setDefaultCommand(launchCaManagerCmd);
        } else {
            ci.addCommand(infoCmd);
            ci.addCommand(removeCmd);
            ci.addCommand(updateCmd);
            ci.addCommand(appSettingsCmd);

            if (suiteInfo.enabled) {
                // setDefaultCommand will add default command first
                ci.setDefaultCommand(launchCmd);
            }
        }

        ci.setItemCommandListener(this);
        append(ci);
        ci.setOwner(this);
    }

    /**
     * Removes a midlet from the App Selector Screen
     *
     * @param suiteInfo the midlet suite info of a recently removed MIDlet
     */
    private void remove(RunningMIDletSuiteInfo suiteInfo) {
        RunningMIDletSuiteInfo msi;

        if (suiteInfo == null) {
            // Invalid parameter, should not happen.
            return;
        }

        // the last item in AppSelector is time
        for (int i = 0; i < size(); i++) {
            msi = (RunningMIDletSuiteInfo)((MidletCustomItem)get(i)).msi;
            if (msi == suiteInfo) {
                PAPICleanUp.removeMissedTransaction(suiteInfo.suiteId);

                if (msi.proxy != null) {
                    msi.proxy.destroyMidlet();
                }

                try {
                    midletSuiteStorage.remove(suiteInfo.suiteId);
                } catch (Throwable t) {
                    if (t instanceof MIDletSuiteLockedException) {
                        String[] val = new String[1];
                        val[0] = suiteInfo.displayName;
                        displayError.showErrorAlert(suiteInfo.displayName,
                            null,
                            Resource.getString(ResourceConstants.ERROR),
                            Resource.getString(
                                ResourceConstants.AMS_MGR_REMOVE_LOCKED_SUITE,
                                    val),
                            this);
                    } else {
                        displayError.showErrorAlert(suiteInfo.displayName,
                            t,
                            Resource.getString(ResourceConstants.ERROR),
                            null, this);
                    }

                    return;
                }

                try {
                    PushRegistryInternal.unregisterConnections(
                        suiteInfo.suiteId);
                } catch (Throwable t) {
                    // Intentionally ignored: suite has been removed already,
                    // we can't do anything meaningful at this point.
                }

                delete(i);
                removeMsi = null;
                break;
            }
        }

        display.setCurrent(this);
    }

    /**
     * Alert the user that an action was successful.
     * @param successMessage message to display to user
     */
    private void displaySuccessMessage(String successMessage) {
        Image icon;
        Alert successAlert;

        icon = GraphicalInstaller.getImageFromInternalStorage("_dukeok8");

        successAlert = new Alert(null, successMessage, icon, null);

        successAlert.setTimeout(GraphicalInstaller.ALERT_TIMEOUT);

        // We need to prevent "flashing" on fast development platforms.
        while (System.currentTimeMillis() - lastDisplayChange <
               GraphicalInstaller.ALERT_TIMEOUT);

        display.setCurrent(successAlert, this);
        lastDisplayChange = System.currentTimeMillis();
    }

    /**
     * Confirm the removal of a suite.
     *
     * @param suiteInfo information for suite to remove
     */
    private void confirmRemove(RunningMIDletSuiteInfo suiteInfo) {
        Form confirmForm;
        StringBuffer temp = new StringBuffer(40);
        Item item;
        String extraConfirmMsg;
        String[] values = new String[1];
        MIDletSuiteImpl midletSuite = null;

        try {
            midletSuite = midletSuiteStorage.getMIDletSuite(suiteInfo.suiteId,
                                                            false);
            confirmForm = new Form(null);

            confirmForm.setTitle(Resource.getString
                                 (ResourceConstants.AMS_CONFIRMATION));

            if (suiteInfo.hasSingleMidlet()) {
                values[0] = suiteInfo.displayName;
            } else {
                values[0] =
                    midletSuite.getProperty(MIDletSuiteImpl.SUITE_NAME_PROP);
            }

            item = new StringItem(null, Resource.getString(
                       ResourceConstants.AMS_MGR_REMOVE_QUE,
                       values));
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            confirmForm.append(item);

            extraConfirmMsg =
                PAPICleanUp.checkMissedTransactions(midletSuite.getID());
            if (extraConfirmMsg != null) {
                temp.setLength(0);
                temp.append(" \n");
                temp.append(extraConfirmMsg);
                item = new StringItem(null, temp.toString());
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                confirmForm.append(item);
            }

            extraConfirmMsg = midletSuite.getProperty("MIDlet-Delete-Confirm");
            if (extraConfirmMsg != null) {
                temp.setLength(0);
                temp.append(" \n");
                temp.append(extraConfirmMsg);
                item = new StringItem(null, temp.toString());
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                confirmForm.append(item);
            }

            if (!suiteInfo.hasSingleMidlet()) {
                temp.setLength(0);
                temp.append(Resource.getString
                            (ResourceConstants.AMS_MGR_SUITE_CONTAINS));
                temp.append(": ");
                item = new StringItem(temp.toString(), "");
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                confirmForm.append(item);
                appendMIDletsToForm(midletSuite, confirmForm);
            }

            String[] recordStores =
                midletSuiteStorage.listRecordStores(suiteInfo.suiteId);
            if (recordStores != null) {
                temp.setLength(0);
                temp.append(Resource.getString
                            (ResourceConstants.AMS_MGR_SUITE_RECORD_STORES));
                temp.append(": ");
                item = new StringItem(temp.toString(), "");
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                confirmForm.append(item);
                appendRecordStoresToForm(recordStores, confirmForm);
            }

            temp.setLength(0);
            temp.append(" \n");
            temp.append(Resource.getString
                        (ResourceConstants.AMS_MGR_REM_REINSTALL, values));
            item = new StringItem("", temp.toString());
            confirmForm.append(item);
        } catch (Throwable t) {
            displayError.showErrorAlert(suiteInfo.displayName, t,
                                   Resource.getString
                                   (ResourceConstants.AMS_CANT_ACCESS),
                                   null);
            return;
        } finally {
            if (midletSuite != null) {
                midletSuite.close();
            }
        }

        confirmForm.addCommand(cancelCmd);
        confirmForm.addCommand(removeOkCmd);
        confirmForm.setCommandListener(this);
        removeMsi = suiteInfo;
        display.setCurrent(confirmForm);
    }

    /**
     * Appends a names of all the MIDlets in a suite to a Form, one per line.
     *
     * @param midletSuite information of a suite of MIDlets
     * @param form form to append to
     */
    private void appendMIDletsToForm(MIDletSuiteImpl midletSuite, Form form) {
        int numberOfMidlets;
        MIDletInfo midletInfo;
        StringItem item;

        numberOfMidlets = midletSuite.getNumberOfMIDlets();
        for (int i = 1; i <= numberOfMidlets; i++) {
            midletInfo = new MIDletInfo(
                             midletSuite.getProperty("MIDlet-" + i));

            item = new StringItem(null, midletInfo.name);
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            form.append(item);
        }
    }

    /**
     * Appends names of the record stores owned by the midlet suite
     * to a Form, one per line.
     *
     * @param recordStores list of the record store names
     * @param form form to append to
     */
    private void appendRecordStoresToForm(String[] recordStores, Form form) {
        StringItem item;

        for (int i = 0; i < recordStores.length; i++) {
            item = new StringItem(null, recordStores[i]);
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            form.append(item);
        }
    }

    /**
     * Open the settings database and retreive an id of the midlet suite
     * that was installed last.
     *
     * @return ID of the midlet suite that was installed last or
     * MIDletSuite.UNUSED_SUITE_ID.
     */
    private int getLastInstalledMIDlet() {
        ByteArrayInputStream bas;
        DataInputStream dis;
        byte[] data;
        RecordStore settings = null;
        int ret = MIDletSuite.UNUSED_SUITE_ID;

        try {
            settings = RecordStore.
                       openRecordStore(GraphicalInstaller.SETTINGS_STORE,
                                       false);

            /** we should be guaranteed that this is always the case! */
            if (settings.getNumRecords() > 0) {

                data = settings.getRecord(
                           GraphicalInstaller.SELECTED_MIDLET_RECORD_ID);

                if (data != null) {
                    bas = new ByteArrayInputStream(data);
                    dis = new DataInputStream(bas);
                    ret = dis.readInt();
                }
            }

        } catch (RecordStoreException e) {
            // ignore
        } catch (IOException e) {
            // ignore
        } finally {
            if (settings != null) {
                try {
                    settings.closeRecordStore();
                } catch (RecordStoreException e) {
                    // ignore
                }
            }
        }

        return ret;
    }

    /**
     * Finds a MidletCustomItem corresponding to the last installed
     * midlet suite.
     * @return the midlet custom item if it was found, null otherwise
     */
    private MidletCustomItem getLastInstalledMidletItem() {
        int installedMidlet = getLastInstalledMIDlet();

        if (installedMidlet != MIDletSuite.UNUSED_SUITE_ID &&
                installedMidlet != MIDletSuite.INTERNAL_SUITE_ID) {
            for (int i = 0; i < size(); i++) {
                MidletCustomItem ci = (MidletCustomItem)get(i);
                if (ci.msi.suiteId == installedMidlet) {
                    return ci;
                }
            }
        }

        return null;
    }

    /**
     * Displayas AppManagerUI with a recently installed midlet hilighted.
     * @return true if display.setCurrentItem() was called,
     *              false - otherwise
     */
    private boolean displayLastInstalledMidlet() {
        MidletCustomItem ci = getLastInstalledMidletItem();

        if (ci != null) {
            display.setCurrentItem(ci);
            return true;
        }

        return false;
    }

    /**
     * Launches the midlet suite described by the given MIDletSuiteInfo.
     * @param msi a structure with information about the midlet suite
     * that must be launched
     */
    private void launchMidlet(RunningMIDletSuiteInfo msi) {
        if (msi.hasSingleMidlet()) {
            manager.launchSuite(msi, msi.midletToRun);
            display.setCurrent(this);
        } else {
            try {
                new MIDletSelector(msi, display, this, manager);
            } catch (Throwable t) {
                displayError.showErrorAlert(msi.displayName, t,
                                            null, null);
            }
        }
    }

    /**
     * Prompts the user to specify whether to launch a midlet from
     * the midlet suite that was just installed.
     */
    private void askUserIfLaunchMidlet() {
        // Ask the user if he wants to run a midlet from
        // the newly installed midlet suite
        String title = Resource.getString(
            ResourceConstants.AMS_MGR_RUN_THE_NEW_SUITE_TITLE, null);
        String msg = Resource.getString(
            ResourceConstants.AMS_MGR_RUN_THE_NEW_SUITE, null);

        Alert alert = new Alert(title, msg, null, AlertType.CONFIRMATION);
        alert.addCommand(runNoCmd);
        alert.addCommand(runYesCmd);
        alert.setCommandListener(this);
        alert.setTimeout(Alert.FOREVER);

        display.setCurrent(alert);
    }

    /** A Timer which will handle firing repaints of the ScrollPainter */
    protected static Timer textScrollTimer;

    /** Text auto-scrolling parameters */
    private static int SCROLL_RATE = 250;

    private static int SCROLL_DELAY = 500;

    private static int SCROLL_SPEED = 10;

    /**
     * Inner class used to display a running midlet in the AppSelector.
     * MidletCustomItem consists of an icon and name associated with the
     * corresponding midlet. In addition if a midlet requests to be
     * put into foreground (requires user attention) an additional
     * system provided icon will be displayed.
     */
    class MidletCustomItem extends CustomItem {

        /**
         * Constructs a midlet representation for the App Selector Screen.
         * @param msi The MIDletSuiteInfo for which representation has
         *            to be created
         */
        MidletCustomItem(RunningMIDletSuiteInfo msi) {
            super(null);
            this.msi = msi;
            icon = msi.icon;
            text = msi.displayName.toCharArray();
            textLen = msi.displayName.length();
            truncWidth = ICON_FONT.charWidth(truncationMark);
            truncated = false;
            if (textScrollTimer == null) {
                textScrollTimer = new Timer();
            }
            xScrollOffset = 0;

        }

        /**
         * Gets the minimum width of a midlet representation in
         * the App Selector Screen.
         * @return the minimum width of a midlet representation
         *         in the App Selector Screen.
         */
        protected int getMinContentWidth() {
            return AppManagerUI.this.getWidth();
        }

        /**
         * Gets the minimum height of a midlet representation in
         * the App Selector Screen.
         * @return the minimum height of a midlet representation
         *         in the App Selector Screen.
         */
        protected int getMinContentHeight() {
            return ICON_BG.getHeight() > ICON_FONT.getHeight() ?
                ICON_BG.getHeight() : ICON_FONT.getHeight();
        }

        /**
         * Gets the preferred width of a midlet representation in
         * the App Selector Screen based on the passed in height.
         * @param height the amount of height available for this Item
         * @return the minimum width of a midlet representation
         *         in the App Selector Screen.
         */
        protected int getPrefContentWidth(int height) {
            return AppManagerUI.this.getWidth();
        }

        /**
         * Gets the preferred height of a midlet representation in
         * the App Selector Screen based on the passed in width.
         * @param width the amount of width available for this Item
         * @return the minimum height of a midlet representation
         *         in the App Selector Screen.
         */
        protected int getPrefContentHeight(int width) {
            return ICON_BG.getHeight() > ICON_FONT.getHeight() ?
                ICON_BG.getHeight() : ICON_FONT.getHeight();
        }

        /**
         * On size change event we define the item's text
         * according to item's new width
         * @param w The current width of this Item
         * @param h The current height of this Item
         */
        protected void sizeChanged(int w, int h) {
            width = w;
            height = h;
            int widthForText = w - ITEM_PAD - ICON_BG.getWidth();
            int msiNameWidth = ICON_FONT.charsWidth(text, 0, textLen);
            scrollWidth = msiNameWidth - widthForText + w/5;
            truncated = msiNameWidth > widthForText;
        }

        /**
         * Paints the content of a midlet representation in
         * the App Selector Screen.
         * Note that icon representing that foreground was requested
         * is painted on to of the existing ickon.
         * @param g The graphics context where painting should be done
         * @param w The width available to this Item
         * @param h The height available to this Item
         */
        protected void paint(Graphics g, int w, int h) {
            int cX = g.getClipX();
            int cY = g.getClipY();
            int cW = g.getClipWidth();
            int cH = g.getClipHeight();

            if ((cW + cX) > bgIconW) {
                if (text != null && h > ICON_FONT.getHeight()) {

                    int color;
                    if (msi.proxy == null) {
                        color = hasFocus ? ICON_HL_TEXT : ICON_TEXT;
                    } else {
                        color = hasFocus ?
                                ICON_RUNNING_HL_TEXT : ICON_RUNNING_TEXT;
                    }

                    g.setColor(color);
                    g.setFont(ICON_FONT);


                    boolean truncate = (xScrollOffset == 0) && truncated;

                    g.clipRect(bgIconW + ITEM_PAD, 0,
                        truncate ? w - truncWidth - bgIconW - 2 * ITEM_PAD :
                                   w - bgIconW - 2 * ITEM_PAD, h);
                    g.drawChars(text, 0, textLen,
                        bgIconW + ITEM_PAD + xScrollOffset, (h - ICON_FONT.getHeight())/2,
                            Graphics.LEFT | Graphics.TOP);
                    g.setClip(cX, cY, cW, cH);

                    if (truncate) {
                        g.drawChar(truncationMark, w - truncWidth,
                            (h - ICON_FONT.getHeight())/2, Graphics.LEFT | Graphics.TOP);
                    }

                }
            }

            if (cX < bgIconW) {
                if (hasFocus) {
                    g.drawImage(ICON_BG, 0, (h - bgIconH)/2,
                                Graphics.TOP | Graphics.LEFT);
                }

                if (icon != null) {
                    g.drawImage(icon, (bgIconW - icon.getWidth())/2,
                                (bgIconH - icon.getHeight())/2,
                                Graphics.TOP | Graphics.LEFT);
                }

                // Draw special icon if user attention is requested and
                // that midlet needs to be brought into foreground by the user
                if (msi.proxy != null && msi.proxy.isAlertWaiting()) {
                    g.drawImage(FG_REQUESTED,
                                bgIconW - FG_REQUESTED.getWidth(), 0,
                                Graphics.TOP | Graphics.LEFT);
                }

                if (!msi.enabled) {
                    // indicate that this suite is disabled
                    g.drawImage(DISABLED_IMAGE,
                                (bgIconW - DISABLED_IMAGE.getWidth())/2,
                                (bgIconH - DISABLED_IMAGE.getHeight())/2,
                                Graphics.TOP | Graphics.LEFT);
                }
            }

        }

        /**
        * Start the scrolling of the text
        */
        protected void startScroll() {
            if (!hasFocus || !truncated) {
                return;
            }
            stopScroll();
            textScrollPainter = new TextScrollPainter();
            textScrollTimer.schedule(textScrollPainter, SCROLL_DELAY, SCROLL_RATE);
        }

        /**
        * Stop the scrolling of the text
        */
        protected void stopScroll() {
            if (textScrollPainter == null) {
                return;
            }
            xScrollOffset = 0;
            textScrollPainter.cancel();
            textScrollPainter = null;
            repaint(bgIconW, 0, width, height);
        }

        /**
        * Called repeatedly to animate a side-scroll effect for text
        */
        protected void repaintScrollText() {
            if (-xScrollOffset < scrollWidth) {
                xScrollOffset -= SCROLL_SPEED;
                repaint(bgIconW, 0, width, height);
            } else {
                // already scrolled to the end of text
                stopScroll();
            }
        }

        /**
         * Handles traversal.
         * @param dir The direction of traversal (Canvas.UP, Canvas.DOWN,
         *            Canvas.LEFT, Canvas.RIGHT)
         * @param viewportWidth The width of the viewport in the AppSelector
         * @param viewportHeight The height of the viewport in the AppSelector
         * @param visRect_inout The return array that tells AppSelector
         *        which portion of the MidletCustomItem has to be made visible
         * @return true if traversal was handled in this method
         *         (this MidletCustomItem just got focus or there was an
         *         internal traversal), otherwise false - to transfer focus
         *         to the next item
         */
        protected boolean traverse(int dir,
                                   int viewportWidth, int viewportHeight,
                                   int visRect_inout[]) {
            // entirely visible and hasFocus
            if (hasFocus) {
                // entirely visible and has focus => transfer focus
                if (visRect_inout[0] <= 0 && visRect_inout[1] <= 0 &&
                    visRect_inout[0] + viewportWidth >= width &&
                    visRect_inout[1] + viewportHeight >= height) {
                    return false;
                }

                // we assume that item is not wider or taller than viewport
                // and scrolling within the item is not an option

            } else {
                hasFocus = true;
                lastSelectedMsi = this.msi;
            }

            visRect_inout[0] = 0;
            visRect_inout[1] = 0;
            visRect_inout[2] = width;
            visRect_inout[3] = height;

            startScroll();

            return true;
        }

        /**
         * Called by the system when a pointer up action
         * The <code>(x,y)</code> coordinates are relative to the origin
         * of the item.
         * @param x the x coordinate of the pointer up
         * @param y the x coordinate of the pointer up
         *
         */
        protected void pointerReleased(int x, int y) {
            if (owner != null && default_command != null) {
                owner.commandAction(default_command, this);
            }
        }


        /**
         * Handles traversal out. This method is called when this
         * MidletCustomItem looses focus.
         */
        protected void traverseOut() {
            hasFocus = false;
            stopScroll();
        }

        /**
         * Repaints MidletCustomItem. Called when internal state changes.
         */
        public void update() {
            repaint();
        }

        /**
         * Sets the owner (AppManagerUI) of this MidletCustomItem
         * @param hs The AppSelector in which this MidletCustomItem is shown
         */
        void setOwner(AppManagerUI hs) {
            owner = hs;
        }

        /**
         * Sets default <code>Command</code> for this <code>Item</code>.
         *
         * @param c the command to be used as this <code>Item's</code> default
         * <code>Command</code>, or <code>null</code> if there is to
         * be no default command
         */
        public void setDefaultCommand(Command c) {
            default_command = c;
            super.setDefaultCommand(c);
        }

        /**
         * Called when MidletCustomItem is shown.
         */
        public void showNotify() {

            // Unfortunately there is no Form.showNotify  method where
            // this could have been done.

            // When icon for the Installer
            // is shown we want to make sure
            // that there are no running midlets from the "internal" suite.
            // The only 2 midlets that can run in bg from
            // "internal" suite are the DiscoveryApp and the Installer.
            // Icon for the Installer will be shown each time
            // the AppSelector is made current since it is the top
            // most icon and we reset the traversal to start from the top
            if (msi.suiteId == MIDletSuite.INTERNAL_SUITE_ID &&
                    appManagerMidlet != null) {
                appManagerMidlet.destroyMidlet();
            }
        }

        /** A TimerTask which will repaint scrolling text  on a repeated basis */
        protected TextScrollPainter textScrollPainter;

        /**
        * Width of the scroll area for text
        */
        protected int scrollWidth;

        /**
        * If text is truncated
        */
        boolean truncated;

        /**
        * pixel offset to the start of the text field  (for example,  if
        * xScrollOffset is -60 it means means that the text in this
        * text field is scrolled 60 pixels left of the left edge of the
        * text field)
        */
        protected int xScrollOffset;

        /**
        * Helper class used to repaint scrolling text
        * if needed.
        */
        private class TextScrollPainter extends TimerTask {
            /**
            * Repaint the item text
            */
            public final void run() {
                repaintScrollText();
            }
        }

        /** True if this MidletCustomItem has focus, and false - otherwise */
        boolean hasFocus; // = false;

        /** The owner of this MidletCustomItem */
        AppManagerUI owner; // = false

        /** The MIDletSuiteInfo associated with this MidletCustomItem */
        RunningMIDletSuiteInfo msi; // = null

        /** The width of this MidletCustomItem */
        int width; // = 0
        /** The height of this MIDletSuiteInfo */
        int height; // = 0

        /** Cashed width of the truncation mark */
        int truncWidth;

        /** The text of this MidletCustomItem */
        char[] text;

        /** Length of the text */
        int textLen;

        /**
         * The icon to be used to draw this midlet representation.
         */
        Image icon; // = null
        /** current default command */
        Command default_command; // = null
    }
}

