FileDocCategorySizeDatePackage
Manager.javaAPI DocJ2ME MIDP 2.048117Thu Nov 07 12:02:24 GMT 2002com.sun.midp.dev

Manager.java

/*
 * @(#)Manager.java	1.71 02/09/11 @(#)
 *
 * Copyright (c) 2001-2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package com.sun.midp.dev;

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

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

import javax.microedition.rms.*;

import com.sun.midp.lcdui.DisplayManagerFactory;
import com.sun.midp.lcdui.Resource;

import com.sun.midp.midlet.*;

import com.sun.midp.midletsuite.*;

import com.sun.midp.security.*;

import com.sun.midp.main.Configuration;

import javax.microedition.io.*;

import com.sun.midp.io.j2me.storage.*;

import com.sun.midp.io.j2me.push.*;

/**
 * The Graphical MIDlet suite manager.
 * <p>
 * Starts with a selector that provides a list of MIDlet suites and
 * a set of commands to perform. It displays the MIDlet names for a suite
 * under the MIDlet suite name, except if there is only one suite then
 * instead of display the suite name, MIDlet-1 name and icon are used.
 * <p>
 * The commands are:</p>
 * <ul>
 * <li><b>Install</b>: Let the user install a suite from a list suites
 * obtained using an HTML URL given by the user. This list is derived by
 * extracting the links with hrefs that are in quotes and end with ".jad" from
 * the HTML page. An href in an extracted link is assumed to be an absolute
 * URL for a MIDP application descriptor.</li>
 * <li><b>Launch</b>: Launch the suite the user selected.
 * <li><b>Remove</b>: Remove the suite (with confirmation) the user selected.
 * </li>
 * <li><b>Update</b>: Update the suite the user selected.</li>
 * <li><b>Info</b>: Show the user general information of the selected suite.
 * <li><b>Settings</b>: Let the user change the manager's settings.
 * </ul>
 */
public class Manager extends MIDlet implements CommandListener {

    /** Translated small copyright string. */
    private static final String SMALL_COPYRIGHT = Resource.getString(
	"Copyright (c) 2000-2002 Sun Microsystems, Inc. All rights reserved.");
    /** Translated long copyright string. */
    private static final String COPYRIGHT = Resource.getString(
	"Copyright (c) 2000-2002 Sun Microsystems, Inc. All rights reserved.\n"
      + "Use is subject to license terms.\n"
      + "Third-party software, including font technology, is copyrighted "
      + "and licensed from Sun suppliers.  Sun, Sun Microsystems, the Sun "
      + "logo, J2ME, the Java Coffee Cup logo, and  Java are trademarks "
      + "or registered trademarks of Sun Microsystems, Inc. in the U.S. "
      + "and other countries.\n"
      + "Federal Acquisitions: Commercial Software - Government Users "
      + "Subject to Standard License Terms and Conditions."
      + "\n\n"  
      + "Copyright (c) 2002 Sun Microsystems, Inc. Tous droits réservés.\n"
      + "Distribué par des licences qui en restreignent l'utilisation.\n"
      + "Le logiciel détenu par des tiers, et qui comprend la technologie "
      + "relative aux polices de caractères, est protégé par un copyright "
      + "et licencié par des fournisseurs de Sun. Sun, Sun Microsystems, "
      + "le logo Sun, J2ME, le logo Java Coffee Cup, et Java sont des "
      + "marques de fabrique ou des marques déposées de Sun Microsystems, "
      + "Inc. aux Etats-Unis et dans d'autres pays.");
    /** Cache of the suite icon. */
    private static Image suiteIcon;
    /** Cache of the empty icon. */
    private static Image emptyIcon;
    /** Cache of the single suite icon. */
    private static Image singleSuiteIcon;
    /** Cache of the Java logo. */
    private static Image javaLogo;
    /** Cache of the home screen graphic. */
    private static Image homeScreenGraphic;
    /** So the static method can know if there is a color display. */
    static boolean colorDisplay;
    /** True until constructed for the first time. */
    private static boolean first = true;

    /** The installer that is being used to install or update a suite. */
    private Installer installer;
    /** List of all the MIDlet suites. */
    private List mlist;
    /** Display for this MIDlet. */
    private Display display;    
    /** Keeps track of when the display last changed, in millseconds. */
    private long lastDisplayChange;
    /** Number of midlets in minfo. */
    private int mcount;
    /** MIDlet suite information, class, name, icon; one per MIDlet suite. */
    private MIDletSuiteInfo[] minfo; 
    /** Currently selected suite. */
    private int selectedSuite;
    /** The application push permission setting. */
    private ChoiceGroup pushChoice;
    /** The application network permission setting. */
    private ChoiceGroup netChoice;
    /** The application network server permission setting. */
    private ChoiceGroup serverChoice;
    /** The application comm port permission setting. */
    private ChoiceGroup commChoice;
    /** Command object for "Apps" command for home screen. */
    private Command appsCmd = new Command(Resource.getString("Apps"), 
                                           Command.SCREEN, 1);
    /** Command object for "Launch". */
    private Command launchCmd = new Command(Resource.getString("Launch"), 
                                           Command.ITEM, 1);
    /** Command object for "Info". */
    private Command infoCmd = new Command(Resource.getString("Info"), 
                                           Command.ITEM, 2);
    /** Command object for "Remove". */
    private Command removeCmd = new Command(Resource.getString("Remove"), 
                                            Command.ITEM, 3);
    /** Command object for "Update". */
    private Command updateCmd = new Command(Resource.getString("Update"), 
                                           Command.ITEM, 4);
    /** Command object for "Application settings". */
    private Command appSettingsCmd = new Command(Resource.getString(
                                      "Application Settings"), 
                                           Command.ITEM, 5);
    /** Command object for "About". */
    private Command aboutCmd = new Command(Resource.getString("About"), 
                                           Command.HELP, 1);
    /** Command object for "OK" command for application settings form. */
    private Command saveAppSettingsCmd = new Command(
                                           Resource.getString("Save"), 
                                           Command.OK, 1);
    /** Command object for "Back" command for back to list. */
    private Command backCmd = new Command(Resource.getString("Back"), 
                                           Command.BACK, 1);
    /** Command object for "Cancel" command for the remove form. */
    private Command cancelCmd = new Command(Resource.getString("Cancel"), 
                                           Command.CANCEL, 1);
    /** Command object for "Remove" command for the remove form. */
    private Command removeOkCmd = new Command(Resource.getString("Remove"), 
                                           Command.SCREEN, 1);

    /**
     * Gets the MIDlet suite icon from storage.
     *
     * @return icon image
     */
    private static Image getSuiteIcon() {
        if (suiteIcon != null) {
            return suiteIcon;
        }

        suiteIcon = GraphicalInstaller.getIconFromStorage(
                        (colorDisplay ? "_suite_8.png" : "_suite_2.png"));
        return suiteIcon;
    }

    /**
     * Gets the empty icon from storage.
     *
     * @return icon image
     */
    private static Image getEmptyIcon() {
        if (emptyIcon != null) {
            return emptyIcon;
        }

        emptyIcon = GraphicalInstaller.getIconFromStorage("_empty.png");
        return emptyIcon;
    }

    /**
     * Gets the single MIDlet suite icon from storage.
     *
     * @return icon image
     */
    private static Image getSingleSuiteIcon() {
        if (singleSuiteIcon != null) {
            return singleSuiteIcon;
        }

        singleSuiteIcon = GraphicalInstaller.getIconFromStorage(
                        (colorDisplay ? "_single8.png" : "_single2.png"));
        return singleSuiteIcon;
    }

    /**
     * Gets the Java logo image from storage.
     *
     * @return icon image
     */
    private static Image getJavaLogo() {
        if (javaLogo != null) {
            return javaLogo;
        }

        javaLogo = GraphicalInstaller.getIconFromStorage(
                       (colorDisplay ? "_logo_8.png" : "_logo_2.png"));
        return javaLogo;
    }

    /**
     * Gets the home screen graphic from storage.
     *
     * @return icon image
     */
    private static Image getHomeScreenGraphic() {
        if (homeScreenGraphic != null) {
            return homeScreenGraphic;
        }

        homeScreenGraphic = GraphicalInstaller.getIconFromStorage(
                       (colorDisplay ? "_home_8.png" : "_home_4.png"));
        return homeScreenGraphic;
    }

    /**
     * Create and initialize a new Manager MIDlet.
     */
    public Manager() {
        installer = Installer.getInstaller();
        display = Display.getDisplay(this);
        colorDisplay = display.isColor();
        mcount = 0;
        minfo = new MIDletSuiteInfo[20];
        readMIDletSuiteInfo();
        first = getAppProperty("logo-displayed").equals("F");
        String runMessage;

        GraphicalInstaller.initSettings();

        setupList();

        if (mcount > 0) {
            mlist.addCommand(infoCmd);
            mlist.addCommand(removeCmd);
            mlist.addCommand(updateCmd);
            mlist.addCommand(appSettingsCmd);
        }

        mlist.addCommand(launchCmd);
        mlist.addCommand(backCmd);
        mlist.addCommand(aboutCmd);
        mlist.setCommandListener(this); // Listen for the selection

        runMessage = getAppProperty("run-message");
        if (runMessage != null) {
            Alert error = new Alert(null);

            error.setString(runMessage);
            display.setCurrent(error, mlist);
        } else if (first) {
            first = false;
            displayHomeScreen();
        } else {
            display.setCurrent(mlist);
        }
    }

    /**
     * Start puts up a List of the MIDlets found in the descriptor file.
     */
    public void startApp() {
    }

    /**
     * Pause; there are no resources that need to be released.
     */
    public void pauseApp() {
    }

    /**
     * Destroy cleans up.
     *
     * @param unconditional is ignored; this object always
     * destroys itself when requested.
     */
    public void destroyApp(boolean unconditional) {
        resetSettings();
    }

    /**
     * Save user settings such as currently selected midlet
     */
    private void resetSettings() {
        GraphicalInstaller.saveSettings(null, "");
    }

    /** Display the home screen. */
    private void displayHomeScreen() {
        Form home = new Form("");

        mlist.setSelectedIndex(0, true);

        home.append(
            new ImageItem(null, getJavaLogo(),
                          ImageItem.LAYOUT_NEWLINE_BEFORE +
                          ImageItem.LAYOUT_CENTER +
                          ImageItem.LAYOUT_NEWLINE_AFTER, null));
        home.append(
            new ImageItem(null, getHomeScreenGraphic(),
                          ImageItem.LAYOUT_NEWLINE_BEFORE +
                          ImageItem.LAYOUT_CENTER +
                          ImageItem.LAYOUT_NEWLINE_AFTER, null));
        home.addCommand(appsCmd);
        home.setCommandListener(this);

        display.setCurrent(home);
    }

    /**
     * Read in and create a MIDletInfo for each MIDlet suite.
     */
    private void readMIDletSuiteInfo() {
        String[] suiteNames;
        MIDletSuite midletSuite;
        int numberOfMidlets;
        String attr;

        suiteNames = installer.list();

        for (int i = 0; i < suiteNames.length; i++) {

            int lowest = i;

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

            try {
                midletSuite = installer.getMIDletSuite(suiteNames[lowest]);
                numberOfMidlets = midletSuite.getNumberOfMIDlets();

                if (numberOfMidlets == 1) {
                    attr = midletSuite.getProperty("MIDlet-1");
                    addMIDletSuite(new MIDletSuiteInfo(suiteNames[lowest],
                                               midletSuite, attr));
                    minfo[mcount - 1].singleMidlet = true;
                    minfo[mcount - 1].icon = getSingleSuiteIcon();
                } else {
                    addMIDletSuite(new MIDletSuiteInfo(suiteNames[lowest],
                                                       midletSuite));
                } 

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

            suiteNames[lowest] = suiteNames[i];
        }
    }

    /**
     * Add a MIDlet suite to the list.
     * @param info MIDlet suite information to add to MIDlet
     */
    private void addMIDletSuite(MIDletSuiteInfo info) {
        if (mcount >= minfo.length) {
            MIDletSuiteInfo[] n = new MIDletSuiteInfo[mcount+4];
            System.arraycopy(minfo, 0, n, 0, mcount);
            minfo = n;
        }

        minfo[mcount++] = info;
    }

    /**
     * Open the settings database and retreive the currently selected midlet
     *
     * @return the storagename of the midlet that shoul be hilighted. this
     *          may be null.
     */
    private String getSelectedMIDlet() {
        ByteArrayInputStream bas;
        DataInputStream dis;
        byte[] data;
        RecordStore settings = null;
        String ret = null;
	
        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.readUTF();
                }
            }

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

        return ret;
    }

    /**
     * Read the set of MIDlet names, icons and classes
     * Fill in the list.
     */
    private void setupList() {
        if (mlist == null) {
            mlist = new List(Resource.getString("Applications"), 
                             Choice.IMPLICIT);

            // Add the installer midlet first
            mlist.append(" " + Resource.getString("Install Application"),
                      getSingleSuiteIcon());
            
            String curMIDlet = getSelectedMIDlet();

            // Add each midlet
            for (int i = 0; i < mcount; i++) {
                mlist.append(" " + minfo[i].displayName, minfo[i].icon);

                if (minfo[i].storageName.equals(curMIDlet)) {
                    // plus one because of the "Install App" option
                    mlist.setSelectedIndex(i + 1, true);
                }
            }
        }
    }
        
    /**
     * Respond to a command issued on any Screen.
     *
     * @param c command activiated by the user
     * @param s the Displayable the command was on.
     */
    public void commandAction(Command c, Displayable s) {
        if (c == aboutCmd) {
            Alert a = new Alert(null);
                
            a.setImage(getJavaLogo());
            a.setString(COPYRIGHT);
            a.setTimeout(Alert.FOREVER);

            display.setCurrent(a, mlist);
            return;
        }

        if (s == mlist) {
            if (c == backCmd) {
                displayHomeScreen();
                return;
            }

            // save the selected suite for the removeOk command
            selectedSuite = mlist.getSelectedIndex();

            // The first suite is built-in suite
            selectedSuite--;

            if (c == List.SELECT_COMMAND || c == launchCmd) {


                if (selectedSuite == -1) {
                    // The built-in Install Application MIDlet was selected.
                    installSuite();
                    return;
                }

                launchSuite(minfo[selectedSuite]);
                return;
            }

            if (selectedSuite == -1) {
                // The built-in Install Application MIDlet was selected.
                displayInstallAppWarning();
                return;
            }

            if (c == infoCmd) {
                displaySuiteInfo(minfo[selectedSuite]);
                return;
            }

            if (c == removeCmd) {
                confirmRemove(minfo[selectedSuite]);
                return;
            }

            if (c == updateCmd) {
                updateSuite(minfo[selectedSuite]);
                return;
            }

            if (c == appSettingsCmd) {
                getApplicationSettings(minfo[selectedSuite]);
                return;
            }
        }

        if (c == removeOkCmd) {
            removeSuite(minfo[selectedSuite]);
            return;
        }

        if (c == saveAppSettingsCmd) {
            saveApplicationSettings(minfo[selectedSuite]);
            return;
        }

        if (c == appsCmd || c == backCmd || c == cancelCmd) {
            // goto back to the main list of suites
            display.setCurrent(mlist);
            return;
        }
    }

    /**
     * Lauches a suite.
     *
     * @param suiteInfo information for suite to launch
     */
    private void launchSuite(MIDletSuiteInfo suiteInfo) {
        try {
            // Create an instance of the MIDlet class
            // All other initialization happens in MIDlet constructor
            if (installer.execute(suiteInfo.storageName, null)) {
                /*
                 * Give the new MIDlet the screen by destroy our self,
                 * because we are running in a limited VM and must
                 * restart the VM let the select suite run.
                 */
                destroyApp(false);
                notifyDestroyed();
            } else {
                // Give the new MIDlet the screen by pausing and
                // asking to be resumed.
                notifyPaused();
                resumeRequest();
            }
        } catch (Exception ex) {
            StringBuffer sb = new StringBuffer();

            sb.append(suiteInfo.displayName);
            sb.append("\n");
            sb.append(Resource.getString("Error"));
            sb.append(ex.toString());

            Alert a = new Alert(Resource.getString("Cannot start: "), 
                                sb.toString(), null, AlertType.ERROR);
            a.setTimeout(Alert.FOREVER);
            display.setCurrent(a, mlist);
        }
    }

    /**
     * Display the information for a suite.
     *
     * @param suiteInfo information for suite to display
     */
    private void displaySuiteInfo(MIDletSuiteInfo suiteInfo) {
        Form infoForm;
        String name;
        Image icon;
        StringBuffer label = new StringBuffer(40);
        StringBuffer value = new StringBuffer(40);
        Item item;
        String temp;

        try {
            infoForm = new Form(null);

            if (suiteInfo.singleMidlet) {
                name = suiteInfo.displayName;
                icon = suiteInfo.icon;
            } else {
                name = suiteInfo.midletSuite.getProperty(
                          Installer.SUITE_NAME_PROP);
                icon = getSuiteIcon();
            }

            label.append(Resource.getString("Info"));
            label.append(": ");
            label.append(name);
            infoForm.setTitle(label.toString());

            infoForm.append(
                new ImageItem(null, icon, ImageItem.LAYOUT_NEWLINE_BEFORE +
                              ImageItem.LAYOUT_CENTER +
                              ImageItem.LAYOUT_NEWLINE_AFTER, null));

            // round up the size to a Kilobyte
            label.setLength(0);
            label.append(Resource.getString("Size"));
            label.append(": ");
            value.append(
                Integer.toString((suiteInfo.midletSuite.getStorageUsed() +
                    1023) / 1024));
            value.append(" K");
            item = new StringItem(label.toString(), value.toString());
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            infoForm.append(item);

            label.setLength(0);
            label.append(Resource.getString("Version"));
            label.append(": ");
            item = new StringItem(label.toString(),
                suiteInfo.midletSuite.getProperty(Installer.VERSION_PROP));
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            infoForm.append(item);

            label.setLength(0);

            if (suiteInfo.midletSuite.isTrusted()) {
                temp = "Authorized Vendor";
            } else {
                temp = "Vendor";
            }

            label.append(Resource.getString(temp));
            label.append(": ");
            item = new StringItem(label.toString(),
                suiteInfo.midletSuite.getProperty(Installer.VENDOR_PROP));
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            infoForm.append(item);

            temp = suiteInfo.midletSuite.getProperty(Installer.DESC_PROP);
            if (temp != null) {
                label.setLength(0);
                label.append(Resource.getString("Description"));
                label.append(": ");
                item = new StringItem(label.toString(), temp);
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                infoForm.append(item);
            }

            if (!suiteInfo.singleMidlet) {
                label.setLength(0);
                label.append(Resource.getString("Contents"));
                label.append(":");
                item = new StringItem(label.toString(), "");
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                infoForm.append(item);
                appendMIDletsToForm(suiteInfo.midletSuite, infoForm);
            }

            label.setLength(0);
            label.append(Resource.getString("Website"));
            label.append(": ");
            item = new StringItem(label.toString(),
                                  suiteInfo.midletSuite.getDownloadUrl());
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            infoForm.append(item);


            label.setLength(0);
            label.append(Resource.getString("Advanced"));
            label.append(": ");
            item = new StringItem(label.toString(), "");
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            infoForm.append(item);

            if (suiteInfo.midletSuite.isTrusted()) {
                infoForm.append(new ImageItem(null,
                    DisplayManagerFactory.getDisplayManager().
                        getTrustedMIDletIcon(), ImageItem.LAYOUT_DEFAULT,
                        null));
                temp = "Trusted";
            } else {
                temp = "Untrusted";
            }

            item = new StringItem(null, Resource.getString(temp));
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            infoForm.append(item);

            temp = suiteInfo.midletSuite.getCA();
            if (temp != null) {
                label.setLength(0);
                label.append(Resource.getString("Authorized by"));
                label.append(": ");
                item = new StringItem(label.toString(), temp);
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                infoForm.append(item);
            }

            temp = PushRegistryImpl.listConnections(
                       suiteInfo.midletSuite.getStorageName(), false);
            if (temp != null) {
                label.setLength(0);
                label.append(Resource.getString("Auto start connections"));
                label.append(": ");
                item = new StringItem(label.toString(), temp);
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                infoForm.append(item);
            }
        } catch (Exception ex) {
            value.setLength(0);
            value.append(suiteInfo.displayName);
            value.append("\n");
            value.append(Resource.getString("Exception"));
            value.append(": ");
            value.append(ex.toString());

            Alert a = new Alert(Resource.getString("Cannot access: "), 
                                value.toString(), null, AlertType.ERROR);
            a.setTimeout(Alert.FOREVER);
            display.setCurrent(a, mlist);
            return;
        }

        infoForm.addCommand(backCmd);
        infoForm.setCommandListener(this);
        display.setCurrent(infoForm);
    }

    /**
     * Get the settings for an application.
     *
     * @param suiteInfo information for suite to display
     */
    private void getApplicationSettings(MIDletSuiteInfo suiteInfo) {
        MIDletSuite midletSuite = suiteInfo.midletSuite;
        Form form;
        String name;
        byte[][] ApiPermissions = suiteInfo.getPermissions();
        byte[] maxLevels = ApiPermissions[Permissions.MAX_LEVELS];
        byte[] curLevels = ApiPermissions[Permissions.CUR_LEVELS];
        int maxLevel;
        int permission;
        String[] values = new String[1];

        try {
            form = new Form(null);

            // A push interrupt may have changed the settings.
            suiteInfo.reloadSuite(installer);

            if (suiteInfo.singleMidlet) {
                name = suiteInfo.displayName;
            } else {
                name = suiteInfo.midletSuite.getProperty(
                          Installer.SUITE_NAME_PROP);
            }

            values[0] = name;
            form.setTitle(Resource.getString("Settings for %1:", values));

            pushChoice = newSettingChoice(form,
                             "Can %1 interrupt another application to " +
                             "receive information? The interrupted " +
                             "application will exit.",
                             Permissions.BLANKET,
                             suiteInfo.getPushInterruptSetting(), name);

            /*
             * Get the best user permission level for the group,
             * so when the user saves all of the permissions will
             * have that level.
             */
            maxLevel = maxLevels[Permissions.HTTP];
            permission = curLevels[Permissions.HTTP];
            maxLevel = selectBestUserPermissionLevel(maxLevel,
                       maxLevels[Permissions.HTTPS]);
            permission = selectBestUserPermissionLevel(permission,
                         curLevels[Permissions.HTTPS]);
            maxLevel = selectBestUserPermissionLevel(maxLevel,
                       maxLevels[Permissions.SSL]);
            permission = selectBestUserPermissionLevel(permission,
                         curLevels[Permissions.SSL]);
            maxLevel = selectBestUserPermissionLevel(maxLevel,
                       maxLevels[Permissions.TCP]);
            permission = selectBestUserPermissionLevel(permission,
                         curLevels[Permissions.TCP]);
            maxLevel = selectBestUserPermissionLevel(maxLevel,
                       maxLevels[Permissions.UDP]);
            permission = selectBestUserPermissionLevel(permission,
                         curLevels[Permissions.UDP]);

            netChoice = newSettingChoice(form,
                            "Can %1 use airtime to SEND information? " +
                            "This may cost you money.", maxLevel,
                            permission, name);

            /*
             * Get the best user permission level for the group,
             * so when the user saves all of the permissions will
             * have that level.
             */
            maxLevel = maxLevels[Permissions.TCP_SERVER];
            permission = curLevels[Permissions.TCP_SERVER];
            maxLevel = selectBestUserPermissionLevel(maxLevel,
                       maxLevels[Permissions.UDP_SERVER]);
            permission = selectBestUserPermissionLevel(permission,
                         curLevels[Permissions.UDP_SERVER]);

            serverChoice = newSettingChoice(form,
                            "Can %1 use airtime to RECEIVE information? " +
                            "This may cost you money.", maxLevel,
                            permission, name);
            
            commChoice = newSettingChoice(form,
                         "Can %1 directly connect to a computer to " +
                         "exchange information? This may require a " +
                         "special cable.", maxLevels[Permissions.COMM],
                         curLevels[Permissions.COMM], name);
        } catch (Exception ex) {
            StringBuffer sb = new StringBuffer();

            sb.append(suiteInfo.displayName);
            sb.append("\n");
            sb.append(Resource.getString("Exception"));
            sb.append(": ");
            sb.append(ex.toString());

            Alert a = new Alert(Resource.getString("Cannot access: "), 
                                sb.toString(), null, AlertType.ERROR);
            a.setTimeout(Alert.FOREVER);
            display.setCurrent(a, mlist);
            return;
        }

        form.addCommand(saveAppSettingsCmd);
        form.addCommand(backCmd);
        form.setCommandListener(this);
        display.setCurrent(form);
    }

    /**
     * Select the best user permission level of either the current
     * permission or the next permission. Return current if neither
     * permission is a user level.
     *
     * @param current current permission level
     * @param next next permission level
     *
     * @return best user level or the current level
     */
    private int selectBestUserPermissionLevel(int current, int next) {
        if (current == Permissions.BLANKET_GRANTED ||
                current == Permissions.BLANKET) {
            return current;
        }

        if (next == Permissions.BLANKET_GRANTED ||
                next == Permissions.BLANKET) {
            return next;
        }

        if (current == Permissions.SESSION ||
                current == Permissions.DENY_SESSION) {
            return current;
        }

        if (next == Permissions.SESSION ||
                next == Permissions.DENY_SESSION) {
            return next;
        }

        if (current == Permissions.DENY ||
                current == Permissions.USER_DENIED) {
            return current;
        }

        if (next == Permissions.DENY ||
                next == Permissions.USER_DENIED) {
            return next;
        }

        return current;
    }
        
    /**
     * Creates a new choice group in a form if it is user settable,
     * with the 3 preset choices and a initial one set.
     * 
     * @param form Form to put the choice in
     * @param question label for the choice, will be translated
     * @param maxLevel maximum permission level
     * @param level current permission level
     * @param name name of suite
     *
     * @return choice to put in the application settings form,
     *           or null if initValue is -1
     */
    private ChoiceGroup newSettingChoice(Form form, String question,
                                         int maxLevel, int level,
                                         String name) {
        String[] values = {name};
        int initValue;
        ChoiceGroup choice;
        
        switch (level) {
        case Permissions.BLANKET_GRANTED:
        case Permissions.BLANKET:
            initValue = 0;
            break;

        case Permissions.ONE_SHOT:
        case Permissions.SESSION:
        case Permissions.DENY_SESSION:
            initValue = 1;
            break;

        case Permissions.DENY:
        case Permissions.USER_DENIED:
            initValue = 2;
            break;

        default:
            return null;
        }

        choice = new ChoiceGroup(Resource.getString(question, values),
                     Choice.EXCLUSIVE);

        if (maxLevel == Permissions.SESSION ||
                maxLevel == Permissions.ONE_SHOT) {
            initValue--;
        } else {
            choice.append(Resource.getString("Yes, always. Don't ask again."),
                          null);
        }

        choice.append(Resource.getString("Maybe. Ask me each time."), null);
        choice.append(Resource.getString("No. Shutoff %1.", values), null);

        choice.setSelectedIndex(initValue, true);

        choice.setPreferredSize(form.getWidth(), -1);

        form.append(choice);

        return choice;
    }

    /**
     * Save the application settings the user entered.
     *
     * @param suiteInfo information for suite to save the settings for
     */
    private void saveApplicationSettings(MIDletSuiteInfo suiteInfo) {
        MIDletSuite midletSuite = suiteInfo.midletSuite;
        byte[][] ApiPermissions = suiteInfo.getPermissions();
        byte[] maxLevels = ApiPermissions[Permissions.MAX_LEVELS];
        byte[] curLevels = ApiPermissions[Permissions.CUR_LEVELS];
        byte interruptSetting = (byte)getNewPermissionLevel(
                                pushChoice, Permissions.BLANKET,
                                suiteInfo.getPushInterruptSetting());

        suiteInfo.setPushInterruptSetting(interruptSetting);

        curLevels[Permissions.PUSH] = getNewPermissionLevel(pushChoice,
                                      maxLevels[Permissions.PUSH],
                                      curLevels[Permissions.PUSH]);

        curLevels[Permissions.HTTP] = getNewPermissionLevel(netChoice,
                                      maxLevels[Permissions.HTTP],
                                      curLevels[Permissions.HTTP]);
        curLevels[Permissions.HTTPS] = getNewPermissionLevel(netChoice,
                                       maxLevels[Permissions.HTTPS],
                                       curLevels[Permissions.HTTPS]);
        curLevels[Permissions.TCP] = getNewPermissionLevel(netChoice,
                                     maxLevels[Permissions.TCP],
                                     curLevels[Permissions.TCP]);
        curLevels[Permissions.SSL] = getNewPermissionLevel(netChoice,
                                     maxLevels[Permissions.SSL],
                                     curLevels[Permissions.SSL]);
        curLevels[Permissions.UDP] = getNewPermissionLevel(netChoice,
                                     maxLevels[Permissions.UDP],
                                     curLevels[Permissions.UDP]);

        curLevels[Permissions.TCP_SERVER] = getNewPermissionLevel(serverChoice,
                                            maxLevels[Permissions.TCP_SERVER],
                                            curLevels[Permissions.TCP_SERVER]);
        curLevels[Permissions.UDP_SERVER] = getNewPermissionLevel(serverChoice,
                                            maxLevels[Permissions.UDP_SERVER],
                                            curLevels[Permissions.UDP_SERVER]);

        curLevels[Permissions.COMM] = getNewPermissionLevel(commChoice,
                                      maxLevels[Permissions.COMM],
                                      curLevels[Permissions.COMM]);

        try {
            Installer.saveSuiteSettings(
                suiteInfo.midletSuite.getStorageRoot(),
                suiteInfo.getPushInterruptSetting(), ApiPermissions,
                suiteInfo.midletSuite.isTrusted());
            displaySuccessMessage(Resource.getString("Saved!"));
        } catch (Exception ex) {
            Alert a = new Alert(Resource.getString("Exception"), 
                                ex.toString(), null, AlertType.ERROR);
            a.setTimeout(Alert.FOREVER);
            display.setCurrent(a, mlist);
        }
    }

    /**
     * Get the choice group index if any and convert it to a new
     * permission. To make this method re-usable, return
     * the current value if the max level is not a user level or
     * if the choice group is null. Also do not return a user level higher
     * than max level.
     *
     * @param choice choice group with the new permission level
     * @param maxLevel maximum level the permission can have
     * @param current current level the permission has
     *
     * @return new level
     */
    byte getNewPermissionLevel(ChoiceGroup choice, int maxLevel,
                               int current) {
        int selected;

        if (maxLevel == Permissions.NEVER ||
                maxLevel == Permissions.ALLOW ||
                choice == null) {
            return (byte)current;
        }

        selected = choice.getSelectedIndex();
        if (choice.size() == 2) {
            // we did not put in the blanket choice, so adjust selected index
            selected++;
        }
        
        switch (choice.getSelectedIndex()) {
        case 0:
            if (maxLevel == Permissions.SESSION) {
                return Permissions.ONE_SHOT;
            }

            if (maxLevel == Permissions.ONE_SHOT) {
                return Permissions.ONE_SHOT;
            }

            return Permissions.BLANKET_GRANTED;

        case 1:
            if (maxLevel == Permissions.ONE_SHOT) {
                return Permissions.ONE_SHOT;
            }

            return Permissions.SESSION;
        }

        return Permissions.USER_DENIED;
    }

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

        try {
            confirmForm = new Form(null);

            confirmForm.setTitle(Resource.getString("Confirmation"));

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

            item = new StringItem(null, Resource.getString(
                      "Are you sure you want to remove %1?", values));
            item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
            confirmForm.append(item);


            extraConfirmMsg = 
                suiteInfo.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.singleMidlet) {
                temp.setLength(0);
                temp.append(Resource.getString("This suite contains"));
                temp.append(": ");
                item = new StringItem(temp.toString(), "");
                item.setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_2);
                confirmForm.append(item);
                appendMIDletsToForm(suiteInfo.midletSuite, confirmForm);
            }

            temp.setLength(0);
            temp.append(" \n");
            temp.append(Resource.getString(
                "Once removed, %1 will have to be reinstalled.", values));
            item = new StringItem("", temp.toString());
            confirmForm.append(item);
        } catch (Exception ex) {
            temp.setLength(0);
            temp.append(suiteInfo.displayName);
            temp.append("\n");
            temp.append(Resource.getString("Exception"));
            temp.append(": ");
            temp.append(ex.toString());

            Alert a = new Alert(Resource.getString("Cannot access: "), 
                                temp.toString(), null, AlertType.ERROR);
            a.setTimeout(Alert.FOREVER);
            display.setCurrent(a, mlist);
            return;
        }

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

    /**
     * Appends a names of all the MIDlets in a suite to a Form, one per line.
     *
     * @param midletSuite suite of MIDlets
     * @param form form to append to
     */
    private void appendMIDletsToForm(MIDletSuite 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);
        }
    }

    /**
     * Remove a suite.
     *
     * @param suiteInfo information for suite to remove
     */
    private void removeSuite(MIDletSuiteInfo suiteInfo) {
        installer.remove(suiteInfo.storageName);
        destroyApp(false);
        notifyDestroyed();
    }

    /**
     * Update a suite.
     *
     * @param suiteInfo information for suite to update
     */
    private void updateSuite(MIDletSuiteInfo suiteInfo) {
        Scheduler.getScheduler().getMIDletSuite().addProperty("storageName",
            suiteInfo.midletSuite.getStorageName());
        installSuite();
    }

    /** Discover and install a suite. */
    private void installSuite() {
        Scheduler.getScheduler().scheduleMIDlet(new GraphicalInstaller());
        destroyApp(false);
        notifyDestroyed();
    }

    /**
     * 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.getIconFromStorage(
                       (colorDisplay ? "_dukeok8.png" : "_dukeok2.png"));

        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, mlist);
        lastDisplayChange = System.currentTimeMillis();
    }

    /**
     * Display the standard warning for Install Application when any
     * command but launch is selected.
     */
    private void displayInstallAppWarning() {
        Alert a = new Alert(null, Resource.getString(
                  "This operation does not apply to Install Application " +
                  "because Install Application is part of the system on " +
                  "this device. Use Install Application to find and " +
                  "install applications."), null, AlertType.INFO);

        a.setTimeout(4000);
        display.setCurrent(a, mlist);
        return;
    }

    /**
     * Simple attribute storage for MIDlet suites
     */
    private class MIDletSuiteInfo {
        /** Midlet suite interface for displaying its info. */
        MIDletSuite midletSuite;
        /** Storage name of the MIDlet suite. */
        String storageName;
        /** Display name of the MIDlet suite. */
        String displayName;
        /** Name of the MIDlet to run. */
        String midletToRun;
        /** Icon for this suite. */
        Image icon;
        /** Is this single MIDlet MIDlet suite. */
        boolean singleMidlet = false;
        /** Holds the updated permissions. */
        private byte[][] permissions;
        /** Holds the updated push interrupt setting. */
        private byte pushInterruptSetting = -1;

        /**
         * Constructs a MIDletSuiteInfo object for a suite.
         *
         * @param theStorageName name the installer has for this suite
         * @param theMidletSuite MIDletSuite object for this suite
         */
        MIDletSuiteInfo(String theStorageName, MIDletSuite theMidletSuite) {
            midletSuite = theMidletSuite;
            displayName = midletSuite.getProperty(Installer.SUITE_NAME_PROP);
            if (displayName == null) {
                displayName = theStorageName;
            }

            icon = getSuiteIcon();
            storageName = theStorageName;
        }

        /**
         * Constructs a MIDletSuiteInfo object for a MIDlet of a suite
         *
         * @param theStorageName name the installer has for this suite
         * @param theMidletSuite MIDletSuite object for this suite
         * @param attr an MIDlet-<n> value for the MIDlet
         */
        MIDletSuiteInfo(String theStorageName, MIDletSuite theMidletSuite,
                        String attr) {
            this(theStorageName, theMidletSuite);

            // use the name from midlet info, if available
            if (attr != null) {
                MIDletInfo midletInfo = new MIDletInfo(attr);
                if (midletInfo.name != null) {
                    displayName = midletInfo.name;
                }
            }
        }

        /**
         * Reloads the suite.
         *
         * @param installer installer to load the midlet suite
         */
        public void reloadSuite(Installer installer) {
            midletSuite = installer.getMIDletSuite(
                            midletSuite.getStorageName());
            permissions = null;
            pushInterruptSetting = -1;
        }

        /**
         * Gets list of permission for this suite.
         *
         * @return permissions from {@link Permissions#}
         */
        public byte[][] getPermissions() {
            if (permissions == null) {
                permissions = midletSuite.getPermissions();
            }

            return permissions;
        }

        /**
         * Sets push setting for interrupting other MIDlets.
         * Reuses the Permissions.
         *
         * @param setting return push setting for interrupting MIDlets the
         *       value will be a permission level from {@link Permissions#}
         */
        public void setPushInterruptSetting(byte setting) {
            pushInterruptSetting = setting;
        }

        /**
         * Gets push setting for interrupting other MIDlets.
         * Reuses the Permissions.
         *
         * @return push setting for interrupting MIDlets the value
         *        will be a permission level from {@link Permissions#}
         */
        public byte getPushInterruptSetting() {
            if (pushInterruptSetting == -1) {
                pushInterruptSetting =
                    (byte)midletSuite.getPushInterruptSetting();
            }

            return pushInterruptSetting;
        }
    }
}