/* * * * Copyright 1990-2009 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 java.util.Enumeration; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import com.sun.midp.i18n.*; import com.sun.midp.midlet.*; import com.sun.midp.installer.*; import com.sun.midp.main.*; import com.sun.midp.configurator.Constants; import com.sun.midp.events.EventQueue; import com.sun.midp.events.NativeEvent; import com.sun.midp.events.EventTypes; /** * This is an implementation of the ApplicationManager interface * for the MVM mode of the VM capable of running with * more than 1 midlet concurrently. * * Application manager controls midlet life cycle: * - installs, updates and removes midlets/midlet suites * - launches, moves to foreground and terminates midlets * - displays info about a midlet/midlet suite * - shuts down the AMS system */ public class MVMManager extends MIDlet implements MIDletProxyListListener, DisplayControllerListener, ApplicationManager, ODTControllerEventConsumer, AMSServicesEventConsumer, InstallerEventConsumer { /** Constant for the discovery application class name. */ private static final String DISCOVERY_APP = "com.sun.midp.installer.DiscoveryApp"; /** Constant for the graphical installer class name. */ private static final String INSTALLER = "com.sun.midp.installer.GraphicalInstaller"; /** Constant for the CA manager class name. */ private static final String CA_MANAGER = "com.sun.midp.appmanager.CaManager"; /** Constant for the Component manager class name. */ private static final String COMP_MANAGER = "com.sun.midp.appmanager.ComponentManager"; /** Constant for the ODT Agent class name. */ private static final String ODT_AGENT = "com.sun.midp.odd.ODTAgentMIDlet"; /** True until constructed for the first time. */ private static boolean first = true; /** Screen that displays all installed midlets and installer */ private AppManagerPeer appManager; /** MIDlet proxy list reference. */ private MIDletProxyList midletProxyList; /** UI to display error alerts. */ private DisplayError displayError; /** If on device debug is active, ID of the suite under debug. */ private int suiteUnderDebugId = MIDletSuite.UNUSED_SUITE_ID; /** A thread waiting until a midlet is terminated. Can be null. */ private Thread waitForDestroyThread; /** A synchronization object. */ private static final Object waitForDestroy = new Object(); /** * Create and initialize a new MVMManager MIDlet. */ public MVMManager() { MIDletProxy thisMidlet; EventQueue eq = EventQueue.getEventQueue(); new ODTControllerEventListener(eq, this); new AMSServicesEventListener(eq, this); new InstallerEventListener(eq, this); midletProxyList = MIDletProxyList.getMIDletProxyList(); // The proxy for this MIDlet may not have been create yet. for (; ; ) { thisMidlet = midletProxyList.findMIDletProxy( MIDletSuite.INTERNAL_SUITE_ID, this.getClass().getName()); if (thisMidlet != null) { break; } try { Thread.sleep(10); } catch (InterruptedException ie) { // ignore } } MVMDisplayController dc = new MVMDisplayController( midletProxyList, thisMidlet); midletProxyList.setDisplayController(dc); dc.addListener(this); IndicatorManager.init(midletProxyList); GraphicalInstaller.initSettings(); first = (getAppProperty("logo-displayed") == null); Display display = Display.getDisplay(this); displayError = new DisplayError(display); // AppManagerUI will be set to be current at the end of its constructor appManager = new AppManagerPeer(this, display, displayError, first, null); /* * Listen to the MIDlet proxy list. * This allows us to notify the Application Selector * of any changes whenever switch back to the AMS. * The listener must be set up after finishing all * initialization in the constructor. */ midletProxyList.addListener(this); processArguments(); if (first) { first = false; } } /** * Processes MIDP_ENABLE_ODD_EVENT */ public void handleEnableODDEvent() { appManager.showODTAgent(); } /** * Processes MIDP_ODD_START_MIDLET_EVENT * * @param suiteId ID of the midlet suite * @param className class name of the midlet to run * @param displayName display name of the midlet to run * @param isDebugMode true if the midlet must be started in debug mode, * false otherwise */ public void handleODDStartMidletEvent(int suiteId, String className, String displayName, boolean isDebugMode) { /* For the case of showing MIDlet selector, we need AMS to have * foreground. */ MIDletProxy thisMidlet = midletProxyList.findMIDletProxy( MIDletSuite.INTERNAL_SUITE_ID, this.getClass().getName()); midletProxyList.setForegroundMIDlet(thisMidlet); if (suiteUnderDebugId != MIDletSuite.UNUSED_SUITE_ID) { /* IMPL NOTE: this forces only one running MIDlet in debug mode - * the VM currently does not support more MIDlets in debug mode * at the same time. */ isDebugMode = false; } try { appManager.launchSuite(suiteId, className, isDebugMode, true); if (isDebugMode) { suiteUnderDebugId = suiteId; } } catch (Exception ex) { displayError.showErrorAlert(displayName, ex, null, null); } } /** * Processes MIDP_ODD_EXIT_MIDLET_EVENT. * * @param suiteId ID of the midlet suite * @param className class name of the midlet to exit or <code>NULL</code> * if all MIDlets from the suite should be exited */ public void handleODDExitMidletEvent(final int suiteId, final String className) { appManager.exitSuite(suiteId, className); } /** * Processes MIDP_ODD_SUITE_INSTALLED_EVENT. This event indicates that * a new MIDlet suite has been installed by ODT agent. It is signal for * the application manager to update the displayed list of MIDlets. * * @param suiteId ID of the newly installed MIDlet suite */ public void handleODDSuiteInstalledEvent(int suiteId) { appManager.notifySuiteInstalled(suiteId); } /** * Processes MIDP_ODD_SUITE_REMOVED_EVENT. This event indicates that * an installed MIDlet suite has been removed by ODT agent. It is signal for * the application manager to update the displayed list of MIDlets. * * @param suiteId ID of the removed MIDlet suite */ public void handleODDSuiteRemovedEvent(int suiteId) { appManager.notifySuiteRemoved(suiteId); } /** * Processes MIDP_KILL_MIDLETS_EVENT. * * @param suiteId ID of the midlet suite from which the midlets * must be killed * @param isolateId ID of the isolate requested this operation */ public void handleKillMIDletsEvent(int suiteId, int isolateId) { try { int timeout = 1000 * Configuration.getIntProperty( "destoryMIDletTimeout", 5); Enumeration proxies = midletProxyList.getMIDlets(); while (proxies.hasMoreElements()) { MIDletProxy mp = (MIDletProxy)proxies.nextElement(); if (mp.getSuiteId() != suiteId) { continue; } // kill the running midlet and wait until it is destroyed mp.destroyMidlet(); try { synchronized(waitForDestroy) { waitForDestroy.wait(timeout); } } catch (InterruptedException ie) { // ignore } } } catch (Exception ex) { displayError.showErrorAlert(null, ex, null, null); } finally { // notify the listeners that the midlets were killed NativeEvent event = new NativeEvent( EventTypes.MIDP_MIDLETS_KILLED_EVENT); event.intParam1 = suiteId; EventQueue eq = EventQueue.getEventQueue(); eq.sendNativeEventToIsolate(event, isolateId); } } /** * Processes RESTART_MIDLET_EVENT. * * @param externalAppId application ID assigned by externall App Manager; * not used if there is no external manager * @param suiteId ID of the midlet suite * @param className class name of the midlet to restart * @param displayName display name of the midlet to restart */ public void handleRestartMIDletEvent(int externalAppId, int suiteId, String className, String displayName) { try { final MIDletProxy mp = midletProxyList.findMIDletProxy(suiteId, className); if (mp != null) { final int id = suiteId; final String nameOfClass = className; final String nameOfMIDlet = displayName; // wait until the midlet is destroyed waitForDestroyThread = new Thread() { public void run() { try { int timeout = 1000 * Configuration.getIntProperty( "destoryMIDletTimeout", 5); synchronized(waitForDestroyThread) { wait(timeout); } } catch(InterruptedException ie) { // ignore } MIDletSuiteUtils.execute(id, nameOfClass, nameOfMIDlet); } }; waitForDestroyThread.start(); mp.destroyMidlet(); } else { // the midlet is not running, just starting it MIDletSuiteUtils.execute(suiteId, className, displayName); } } catch (Exception ex) { displayError.showErrorAlert(displayName, ex, null, null); } } // ================================================================= // ---------- Operations that can be performed on Midlets ---------- /** * Start app; there is nothing that needs to be done at start up. */ public void startApp() { } /** * Pause; there are no resources that need to be released. */ public void pauseApp() { } /** * Destroy midlet. Cleans up the resources used. * * @param unconditional is ignored; this object always * destroys itself when requested. */ public void destroyApp(boolean unconditional) { /* * Save user settings such as currently selected MIDlet * This may not be needed since we are always running * IMPL_NOTE: remove this */ GraphicalInstaller.saveSettings(null, MIDletSuite.UNUSED_SUITE_ID); appManager.cleanUp(); // Ending the MIDlet ends all others. midletProxyList.shutdown(); } // ============================================================== // ------ Implementation of the DisplayControllerListener interface /** * Called when going to select midlet to * bring it to foreground. * * @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 selectForeground(boolean onlyFromLaunchedList) { appManager.showMidletSwitcher(onlyFromLaunchedList); } // ============================================================== // ------ Implementation of the MIDletProxyListListener interface /** * Called when a MIDlet is added to the list. * * @param midlet The proxy of the MIDlet being added */ public void midletAdded(MIDletProxy midlet) { appManager.notifyMidletStarted(midlet); } /** * Called when the state of a MIDlet in the list is updated. * * @param midlet The proxy of the MIDlet that was updated * @param fieldId code for which field of the proxy was updated */ public void midletUpdated(MIDletProxy midlet, int fieldId) { appManager.notifyMidletStateChanged(midlet); } /** * Called when a MIDlet is removed from the list. * * @param midlet The proxy of the removed MIDlet */ public void midletRemoved(MIDletProxy midlet) { synchronized(waitForDestroy) { waitForDestroy.notify(); } appManager.notifyMidletExited(midlet); } /** * Requests ODT agent to exit running commands for the given suite. * @param suiteInfo Suite whose commands should be exited. */ private void exitODTCommand(RunningMIDletSuiteInfo suiteInfo) { MIDletProxy odtAgentMidlet = midletProxyList.findMIDletProxy( MIDletSuite.INTERNAL_SUITE_ID, ODT_AGENT); if (odtAgentMidlet != null) { EventQueue eq = EventQueue.getEventQueue(); NativeEvent event = new NativeEvent( EventTypes.MIDP_ODD_MIDLET_EXITED_EVENT); event.intParam1 = suiteInfo.suiteId; event.stringParam1 = odtAgentMidlet.getClassName(); eq.sendNativeEventToIsolate(event, odtAgentMidlet.getIsolateId()); } if (suiteUnderDebugId == suiteInfo.suiteId) { suiteUnderDebugId = MIDletSuite.UNUSED_SUITE_ID; } } /** * Called when a suite exited (last running MIDlet in suite exited). * @param suiteInfo Suite which just exited * @param className the running MIDlet class name */ public void notifySuiteExited(RunningMIDletSuiteInfo suiteInfo, String className) { if (!suiteInfo.isLocked()) { exitODTCommand(suiteInfo); } appManager.notifySuiteExited(suiteInfo); if (waitForDestroyThread != null) { synchronized(waitForDestroyThread) { waitForDestroyThread.notify(); waitForDestroyThread = null; } } } /** * Handle exit of MIDlet selector. * @param suiteInfo Containing ID of suite */ public void notifyMIDletSelectorExited(RunningMIDletSuiteInfo suiteInfo) { if (!suiteInfo.isLocked()) { exitODTCommand(suiteInfo); } appManager.notifyMIDletSelectorExited(suiteInfo); } /** * Called when error occurred while starting a MIDlet object. * * @param externalAppId ID assigned by the external application manager * @param suiteId Suite ID of the MIDlet * @param className Class name of the MIDlet * @param errorCode start error code * @param errorDetails start error details */ public void midletStartError(int externalAppId, int suiteId, String className, int errorCode, String errorDetails) { appManager.notifyMidletStartError(suiteId, className, errorCode, errorDetails); } // ============================================================== // ------ Implementation of the MIDletProxyListListener interface /** Discover and install a suite. */ public void installSuite() { try { MIDletSuiteUtils.execute(MIDletSuite.INTERNAL_SUITE_ID, DISCOVERY_APP, Resource.getString(ResourceConstants.INSTALL_APPLICATION)); } catch (Exception ex) { displayError.showErrorAlert(Resource.getString( ResourceConstants.INSTALL_APPLICATION), ex, null, null); } } /** Launch the CA manager. */ public void launchCaManager() { try { MIDletSuiteUtils.execute(MIDletSuite.INTERNAL_SUITE_ID, CA_MANAGER, Resource.getString(ResourceConstants.CA_MANAGER_APP)); } catch (Exception ex) { displayError.showErrorAlert(Resource.getString( ResourceConstants.CA_MANAGER_APP), ex, null, null); } } /** Launch the Component manager. */ public void launchComponentManager() { try { MIDletSuiteUtils.execute(MIDletSuite.INTERNAL_SUITE_ID, COMP_MANAGER, Resource.getString(ResourceConstants.COMP_MANAGER_APP)); } catch (Exception ex) { displayError.showErrorAlert(Resource.getString( ResourceConstants.COMP_MANAGER_APP), ex, null, null); } } /** Launch the ODT agent. */ public void launchODTAgent() { launchODTAgent(null); } /** * Launches a suite. * * @param suiteInfo information for suite to launch * @param midletToRun class name of the MIDlet to launch */ public void launchSuite(RunningMIDletSuiteInfo suiteInfo, String midletToRun) { if (Constants.MEASURE_STARTUP) { System.err.println("Application Startup Time: Begin at " +System.currentTimeMillis()); } try { // Create an instance of the MIDlet class // All other initialization happens in MIDlet constructor MIDletSuiteUtils.execute(suiteInfo.suiteId, midletToRun, null, suiteInfo.isDebugMode); } catch (Exception ex) { displayError.showErrorAlert(suiteInfo.displayName, ex, null, null); } } /** * Update a suite. * * @param suiteInfo information for suite to update */ public void updateSuite(RunningMIDletSuiteInfo suiteInfo) { /* * Setting arg 0 to "U" signals that arg 1 is a suite ID for updating. */ try { MIDletSuiteUtils.executeWithArgs(MIDletSuite.INTERNAL_SUITE_ID, INSTALLER, suiteInfo.displayName, "U", String.valueOf(suiteInfo.suiteId), null); } catch (Exception ex) { displayError.showErrorAlert(suiteInfo.displayName, ex, null, null); } } /** * Shut down the system */ public void shutDown() { midletProxyList.shutdown(); } /** * Bring the midlet with the passed in midlet suite info to the * foreground. * * @param suiteInfo information for the midlet to be put to foreground * @param className the running MIDlet class name */ public void moveToForeground(RunningMIDletSuiteInfo suiteInfo, String className) { try { if (Constants.MEASURE_STARTUP) { System.err.println("Switch To Foreground Time: Begin at " + System.currentTimeMillis()); } if (suiteInfo != null) { midletProxyList.setForegroundMIDlet(suiteInfo.getProxyFor(className)); } } catch (Exception ex) { displayError.showErrorAlert(suiteInfo.displayName, ex, null, null); } } /** * Exit the midlet with the passed in midlet suite info. * * @param suiteInfo information for the midlet to be terminated * @param className the running MIDlet class name */ public void exitMidlet(RunningMIDletSuiteInfo suiteInfo, String className) { try { if (suiteInfo != null) { MIDletProxy proxy = suiteInfo.getProxyFor(className); if (proxy != null) { proxy.destroyMidlet(); } } } catch (Exception ex) { displayError.showErrorAlert(suiteInfo.displayName, ex, null, null); } } /** * Processes the arguments of this MIDletSuite. */ private void processArguments() { for (int i = 0; i < 3; ++i) { final String argValue = getAppProperty("arg-" + i); if (argValue == null) { // no more arguments break; } if (argValue.equals("-runodtagent")) { launchODTAgent(null); } else if (argValue.startsWith("-runodtagent:")) { final int colonIndex = argValue.indexOf(':'); final String odtAgentSettings = argValue.substring(colonIndex + 1); launchODTAgent(odtAgentSettings); } } } /** * Launches the ODT agent with the given settings. * * @param odtAgentSettings string containing the ODT agent settings or * <code>null</code> if the default settings should be used */ private void launchODTAgent(final String odtAgentSettings) { try { MIDletSuiteUtils.executeWithArgs( MIDletSuite.INTERNAL_SUITE_ID, ODT_AGENT, Resource.getString(ResourceConstants.ODT_AGENT_MIDLET), odtAgentSettings, null, null); } catch (final Exception ex) { displayError.showErrorAlert( Resource.getString(ResourceConstants.ODT_AGENT_MIDLET), ex, null, null); } } }