0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.midp.installer;
0028:
0029: import java.util.Vector;
0030:
0031: import java.io.IOException;
0032: import java.io.InputStream;
0033: import java.io.OutputStream;
0034: import java.io.ByteArrayInputStream;
0035:
0036: import javax.microedition.io.ConnectionNotFoundException;
0037:
0038: import com.sun.midp.security.SecurityHandler;
0039: import com.sun.midp.security.SecurityToken;
0040: import com.sun.midp.security.Permissions;
0041:
0042: import com.sun.midp.main.MIDletSuiteVerifier;
0043: import com.sun.midp.main.MIDletAppImageGenerator;
0044:
0045: import com.sun.midp.midlet.MIDletStateHandler;
0046: import com.sun.midp.midlet.MIDletSuite;
0047:
0048: import com.sun.midp.midletsuite.MIDletSuiteStorage;
0049: import com.sun.midp.midletsuite.MIDletSuiteImpl;
0050: import com.sun.midp.midletsuite.MIDletInfo;
0051: import com.sun.midp.midletsuite.MIDletSuiteInfo;
0052: import com.sun.midp.midletsuite.InstallInfo;
0053: import com.sun.midp.midletsuite.SuiteSettings;
0054: import com.sun.midp.midletsuite.MIDletSuiteLockedException;
0055: import com.sun.midp.midletsuite.MIDletSuiteCorruptedException;
0056:
0057: import com.sun.midp.io.HttpUrl;
0058: import com.sun.midp.io.Util;
0059:
0060: import com.sun.midp.io.j2me.push.PushRegistryInternal;
0061: import com.sun.midp.io.j2me.storage.RandomAccessStream;
0062: import com.sun.midp.io.j2me.storage.File;
0063:
0064: import com.sun.midp.rms.RecordStoreFactory;
0065:
0066: import com.sun.midp.content.CHManager;
0067:
0068: import com.sun.midp.log.Logging;
0069: import com.sun.midp.log.LogChannels;
0070:
0071: import com.sun.midp.configurator.Constants;
0072:
0073: /**
0074: * An Installer manages MIDlet suites and libraries
0075: * present in a Java application environment. An MIDlet suite
0076: * distributed as a descriptor and JAR pair.
0077: * The descriptor identifies the configuration and contains security
0078: * information and the manifest of the JAR describes the contents.
0079: * The implementation of an Installer is
0080: * specific to the platform and provides access to
0081: * procedures that make an MIDlet suite visible to users.
0082: * <P>
0083: * Each installed package is uniquely identified by a storage name
0084: * constructed from the combination
0085: * of the values of the <code>MIDlet-Name</code> and
0086: * <code>MIDlet-Vendor</code> attributes.
0087: * The syntax and content of the strings used to identify
0088: * installed packages are implementation dependent.
0089: * Only packages installed or upgraded using this API appear
0090: * in the list of known packages.
0091: *
0092: */
0093: public abstract class Installer {
0094: /** Status code to signal connection to the JAD server was successful. */
0095: public static final int DOWNLOADING_JAD = 1;
0096:
0097: /** Status code to signal that another 1K of the JAD has been download. */
0098: public static final int DOWNLOADED_1K_OF_JAD = 2;
0099:
0100: /** Status code to signal connection to the JAR server was successful. */
0101: public static final int DOWNLOADING_JAR = 3;
0102:
0103: /** Status code to signal that another 1K of the JAR has been download. */
0104: public static final int DOWNLOADED_1K_OF_JAR = 4;
0105:
0106: /**
0107: * Status code to signal that download is done and the suite is being
0108: * verified.
0109: */
0110: public static final int VERIFYING_SUITE = 5;
0111:
0112: /**
0113: * Status code to signal that application image is being generating.
0114: */
0115: public static final int GENERATING_APP_IMAGE = 6;
0116:
0117: /**
0118: * Status code to signal that suite classes are being verified.
0119: */
0120: public static final int VERIFYING_SUITE_CLASSES = 7;
0121:
0122: /**
0123: * Status code for local writing of the verified MIDlet suite.
0124: * Stopping the install at this point has no effect, so there user
0125: * should not be given a chance to stop the install.
0126: */
0127: public static final int STORING_SUITE = 8;
0128:
0129: /** Status code for corrupted suite */
0130: public static final int CORRUPTED_SUITE = 9;
0131:
0132: /** System property containing the supported microedition profiles */
0133: protected static final String MICROEDITION_PROFILES = "microedition.profiles";
0134:
0135: /** System property containing the microedition configuration */
0136: protected static final String MICROEDITION_CONFIG = "microedition.configuration";
0137:
0138: /** System property containing the microedition locale */
0139: protected static final String MICROEDITION_LOCALE = "microedition.locale";
0140:
0141: /** Media-Type for valid application descriptor files. */
0142: public static final String JAD_MT = "text/vnd.sun.j2me.app-descriptor";
0143:
0144: /** Media-Type for valid Jar file. */
0145: public static final String JAR_MT_1 = "application/java";
0146:
0147: /** Media-Type for valid Jar file. */
0148: public static final String JAR_MT_2 = "application/java-archive";
0149:
0150: /**
0151: * Filename to save the JAR of the suite temporarily. This is used
0152: * to avoid overwriting an existing JAR prior to verification.
0153: */
0154: protected static final String TMP_FILENAME = "installer.tmp";
0155:
0156: /** Midlet suite signature verifier. */
0157: protected Verifier verifier;
0158:
0159: /** Holds the install state. */
0160: protected InstallStateImpl state;
0161:
0162: /** An alias for more state.installInfo to get more compact record. */
0163: protected InstallInfo info;
0164:
0165: /** An alias for more state.suiteSettings to get more compact record. */
0166: protected SuiteSettings settings;
0167:
0168: /** Holds the CLDC configuration string. */
0169: protected String cldcConfig;
0170:
0171: /** Holds the device's Runtime Execution Environment string. */
0172: protected final String cldcRuntimeEnv = "MIDP.CLDC";
0173:
0174: /** Holds the MIDP supported profiles. */
0175: private Vector supportedProfiles;
0176:
0177: /** Use this to be the security domain for unsigned suites. */
0178: protected String unsignedSecurityDomain = Permissions.UNIDENTIFIED_DOMAIN_BINDING;
0179:
0180: /**
0181: * Include this permissions into the list of permissions
0182: * given in MIDlet-Permissions jad attribute for unsigned
0183: * suites.
0184: */
0185: protected String additionalPermissions;
0186:
0187: /**
0188: * Constructor of the Installer.
0189: */
0190: Installer() {
0191: state = getInstallState();
0192: verifier = new VerifierImpl(state);
0193:
0194: /* Aliases for more compact record. */
0195: info = state.installInfo;
0196: settings = state.suiteSettings;
0197: }
0198:
0199: /**
0200: * Creates an instance of InstallState of the appropriate type
0201: * depending on the installer type. Should be overloaded in the
0202: * inherited classes.
0203: *
0204: * @return an instance of class containing the installation state
0205: */
0206: protected InstallStateImpl getInstallState() {
0207: if (state == null) {
0208: state = new InstallStateImpl();
0209: // IMPL_NOTE: "info" and "settings" aliases must be updated
0210: // after calling getInstallState().
0211: }
0212:
0213: return state;
0214: }
0215:
0216: /**
0217: * Installs a software package from the given URL. The URL is assumed
0218: * refer to an application descriptor.
0219: * <p>
0220: * If the component to be installed is the same as an existing
0221: * component (by comparing the <code>MIDlet-Name</code>,
0222: * <code>MIDlet-Vendor</code> attributes)
0223: * then this install is an upgrade if the version number is greater
0224: * than the current version. If so, the new version replaces in its
0225: * entirety the current version.
0226: * <p>
0227: * It is implementation dependent when the upgraded component is
0228: * made available for use.
0229: * <p>
0230: * The implementation of install must be robust in the presence
0231: * of failures such as running out of memory. If this method
0232: * throws an exception then the package must <em>not</em> be installed
0233: * and any previous version of the component must be left intact
0234: * and operational.
0235: * <p>
0236: * To receive status updates and installer warnings, provide an install
0237: * listener. If no listener is provided all warnings will be thrown
0238: * as exceptions.
0239: *
0240: * @param location the URL from which the application descriptor can be
0241: * updated
0242: * @param storageId ID of the storage where the suite should be saved
0243: * @param force if <code>true</code> the MIDlet suite components to be
0244: * installed will overwrite any existing components without
0245: * any version comparison
0246: * @param removeRMS if <code>true</code> and existing RMS data will be
0247: * removed when overwriting an existing suite
0248: * @param installListener object to receive status updates and install
0249: * warnings, can be null
0250: *
0251: * @return the unique ID of the installed package.
0252: *
0253: * @exception ConnectionNotFoundException if JAD URL is invalid
0254: * @exception IOException is thrown if any error prevents the installation
0255: * of the MIDlet suite, including being unable to access the application
0256: * descriptor or JAR
0257: * @exception InvalidJadException if the downloaded application descriptor
0258: * is invalid
0259: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
0260: * locked
0261: * @exception SecurityException if the caller does not have permission
0262: * to install software
0263: * @exception IllegalArgumentException is thrown, if the location of the
0264: * descriptor file is not specified
0265: */
0266: public int installJad(String location, int storageId,
0267: boolean force, boolean removeRMS,
0268: InstallListener installListener) throws IOException,
0269: InvalidJadException, MIDletSuiteLockedException,
0270: SecurityException {
0271:
0272: info.jadUrl = location;
0273: state.force = force;
0274: state.removeRMS = removeRMS;
0275: state.nextStep = 1;
0276: state.listener = installListener;
0277: state.chmanager = CHManager.getManager(null);
0278: state.storageId = storageId;
0279:
0280: return performInstall();
0281: }
0282:
0283: /**
0284: * Installs a software package from the given URL. The URL is assumed
0285: * refer to a JAR.
0286: * <p>
0287: * If the component to be installed is the same as an existing
0288: * component (by comparing the <code>MIDlet-Name</code>,
0289: * <code>MIDlet-Vendor</code> attributes)
0290: * then this install is an upgrade if the version number is greater
0291: * than the current version. If so, the new version replaces in its
0292: * entirety the current version.
0293: * <p>
0294: * It is implementation dependent when the upgraded component is
0295: * made available for use.
0296: * <p>
0297: * The implementation of install must be robust in the presence
0298: * of failures such as running out of memory. If this method
0299: * throws an exception then the package must <em>not</em> be installed
0300: * and any previous version of the component must be left intact
0301: * and operational.
0302: * <p>
0303: * To receive status updates and installer warnings, provide an install
0304: * listener. If no listener is provided all warnings will be thrown
0305: * as exceptions.
0306: *
0307: * @param location the URL from which the JAR can be updated
0308: * @param name the name of the suite to be updated
0309: * @param storageId ID of the storage where the suite should be saved
0310: * @param force if <code>true</code> the MIDlet suite components to be
0311: * installed will overwrite any existing components without
0312: * any version comparison
0313: * @param removeRMS if <code>true</code> and existing RMS data will be
0314: * removed when overwriting an existing suite
0315: * @param installListener object to receive status updates and install
0316: * warnings, can be null
0317: *
0318: * @return the unique ID of the installed package.
0319: *
0320: * @exception IOException is thrown if any error prevents the installation
0321: * of the MIDlet suite, including being unable to access the JAR
0322: * @exception InvalidJadException if the downloaded JAR is invalid
0323: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
0324: * locked
0325: * @exception SecurityException if the caller does not have permission
0326: * to install software
0327: * @exception IllegalArgumentException is thrown, if the location of the
0328: * JAR specified
0329: */
0330: public int installJar(String location, String name, int storageId,
0331: boolean force, boolean removeRMS,
0332: InstallListener installListener) throws IOException,
0333: InvalidJadException, MIDletSuiteLockedException {
0334:
0335: if (location == null || location.length() == 0) {
0336: throw new IllegalArgumentException(
0337: "Must specify URL of .jar file");
0338: }
0339:
0340: info.jadUrl = null;
0341: info.jarUrl = location;
0342: info.suiteName = name;
0343: state.force = force;
0344: state.removeRMS = removeRMS;
0345: state.file = new File();
0346: state.nextStep = 5;
0347: state.listener = installListener;
0348: state.chmanager = CHManager.getManager(null);
0349: state.storageId = storageId;
0350:
0351: return performInstall();
0352: }
0353:
0354: /**
0355: * Performs an install.
0356: *
0357: * @return the unique name of the installed package
0358: *
0359: * @exception IOException is thrown, if an I/O error occurs during
0360: * descriptor or jar file download
0361: * @exception InvalidJadException is thrown, if the descriptor file is not
0362: * properly formatted or does not contain the required
0363: * information
0364: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
0365: * locked
0366: * @exception IllegalArgumentException is thrown, if the
0367: * descriptor file is not specified
0368: */
0369: protected int performInstall() throws IOException,
0370: InvalidJadException, MIDletSuiteLockedException {
0371:
0372: state.midletSuiteStorage = MIDletSuiteStorage
0373: .getMIDletSuiteStorage();
0374:
0375: /* Disable push interruptions during install. */
0376: PushRegistryInternal.enablePushLaunch(false);
0377:
0378: try {
0379: state.startTime = System.currentTimeMillis();
0380:
0381: while (state.nextStep < 9) {
0382: /*
0383: * clear the previous warning, so we can tell if another has
0384: * happened
0385: */
0386: state.exception = null;
0387:
0388: if (state.stopInstallation) {
0389: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0390: throw new IOException("stopped");
0391: }
0392:
0393: switch (state.nextStep) {
0394: case 1:
0395: installStep1();
0396: break;
0397:
0398: case 2:
0399: installStep2();
0400: break;
0401:
0402: case 3:
0403: installStep3();
0404: break;
0405:
0406: case 4:
0407: installStep4();
0408: break;
0409:
0410: case 5:
0411: installStep5();
0412: break;
0413:
0414: case 6:
0415: installStep6();
0416: break;
0417:
0418: case 7:
0419: installStep7();
0420: break;
0421:
0422: case 8:
0423: installStep8();
0424: break;
0425:
0426: default:
0427: // for safety/completeness.
0428: Logging.report(Logging.CRITICAL,
0429: LogChannels.LC_AMS,
0430: "Installer: Unknown step: "
0431: + state.nextStep);
0432: break;
0433: }
0434:
0435: if (state.exception != null) {
0436: if (state.listener == null) {
0437: throw state.exception;
0438: }
0439:
0440: if (!state.listener.warnUser(state)) {
0441: state.stopInstallation = true;
0442: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0443: throw state.exception;
0444: }
0445: }
0446: }
0447: } finally {
0448: if (state.previousSuite != null) {
0449: state.previousSuite.close();
0450: }
0451: if (info.jarFilename != null) {
0452: if (state.file.exists(info.jarFilename)) {
0453: try {
0454: state.file.delete(info.jarFilename);
0455: } catch (Exception e) {
0456: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0457: Logging.report(Logging.WARNING,
0458: LogChannels.LC_AMS,
0459: "delete file threw an Exception");
0460: }
0461: }
0462: }
0463: }
0464:
0465: PushRegistryInternal.enablePushLaunch(true);
0466: }
0467:
0468: return info.id;
0469: }
0470:
0471: /**
0472: * Downloads the JAD, save it in the install state.
0473: * Parse the JAD, make sure it has
0474: * the required properties, and save them in the install state.
0475: *
0476: * @exception IOException is thrown, if an I/O error occurs during
0477: * descriptor or jar file download
0478: * @exception InvalidJadException is thrown, if the descriptor file is not
0479: * properly formatted or does not contain the required attributes
0480: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
0481: * locked
0482: * @exception IllegalArgumentException is thrown, if the
0483: * descriptor file is not specified
0484: */
0485: private void installStep1() throws IOException,
0486: InvalidJadException, MIDletSuiteLockedException {
0487:
0488: if (info.jadUrl == null || info.jadUrl.length() == 0) {
0489: throw new IllegalArgumentException(
0490: "Must specify URL of .jad file");
0491: }
0492:
0493: try {
0494: state.jad = downloadJAD();
0495: } catch (OutOfMemoryError e) {
0496: try {
0497: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0498: } catch (Throwable t) {
0499: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0500: Logging.report(Logging.WARNING, LogChannels.LC_AMS,
0501: "Throwable during posting install message");
0502: }
0503: }
0504:
0505: throw new InvalidJadException(
0506: InvalidJadException.TOO_MANY_PROPS);
0507: }
0508:
0509: if (state.exception != null) {
0510: return;
0511: }
0512:
0513: state.jadProps = new JadProperties();
0514: try {
0515: state.jadProps.load(new ByteArrayInputStream(state.jad),
0516: state.jadEncoding);
0517: } catch (OutOfMemoryError e) {
0518: state.jad = null;
0519: try {
0520: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0521: } catch (Throwable t) {
0522: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0523: Logging.report(Logging.WARNING, LogChannels.LC_AMS,
0524: "Throwable during posting install message");
0525: }
0526: }
0527:
0528: throw new InvalidJadException(
0529: InvalidJadException.TOO_MANY_PROPS);
0530: } catch (InvalidJadException ije) {
0531: state.jad = null;
0532: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0533: throw ije;
0534: } catch (java.io.UnsupportedEncodingException uee) {
0535: state.jad = null;
0536: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0537: throw new InvalidJadException(
0538: InvalidJadException.UNSUPPORTED_CHAR_ENCODING,
0539: state.jadEncoding);
0540: }
0541:
0542: info.suiteName = state.jadProps
0543: .getProperty(MIDletSuite.SUITE_NAME_PROP);
0544: if (info.suiteName == null || info.suiteName.length() == 0) {
0545: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0546: throw new InvalidJadException(
0547: InvalidJadException.MISSING_SUITE_NAME);
0548: }
0549:
0550: info.suiteVendor = state.jadProps
0551: .getProperty(MIDletSuite.VENDOR_PROP);
0552: if (info.suiteVendor == null || info.suiteVendor.length() == 0) {
0553: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0554: throw new InvalidJadException(
0555: InvalidJadException.MISSING_VENDOR);
0556: }
0557:
0558: info.suiteVersion = state.jadProps
0559: .getProperty(MIDletSuite.VERSION_PROP);
0560: if (info.suiteVersion == null
0561: || info.suiteVersion.length() == 0) {
0562: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0563: throw new InvalidJadException(
0564: InvalidJadException.MISSING_VERSION);
0565: }
0566:
0567: try {
0568: checkVersionFormat(info.suiteVersion);
0569: } catch (NumberFormatException nfe) {
0570: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0571: throw new InvalidJadException(
0572: InvalidJadException.INVALID_VERSION);
0573: }
0574:
0575: info.id = state.midletSuiteStorage.createSuiteID();
0576:
0577: checkPreviousVersion();
0578: state.nextStep++;
0579: }
0580:
0581: /**
0582: * If the JAD belongs to an installed suite, check the URL against the
0583: * installed one.
0584: */
0585: private void installStep2() {
0586: state.nextStep++;
0587:
0588: if (state.isPreviousVersion) {
0589: checkForDifferentDomains(info.jadUrl);
0590: }
0591: }
0592:
0593: /**
0594: * Makes sure the suite can fit in storage.
0595: *
0596: * @exception IOException is thrown, if an I/O error occurs during
0597: * descriptor or jar file download
0598: * @exception InvalidJadException is thrown, if the descriptor file is not
0599: * properly formatted or does not contain the required
0600: */
0601: private void installStep3() throws IOException, InvalidJadException {
0602: String sizeString;
0603: int dataSize;
0604: int suiteSize;
0605:
0606: sizeString = state.jadProps
0607: .getProperty(MIDletSuite.JAR_SIZE_PROP);
0608: if (sizeString == null) {
0609: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0610: throw new InvalidJadException(
0611: InvalidJadException.MISSING_JAR_SIZE);
0612: }
0613:
0614: try {
0615: info.expectedJarSize = Integer.parseInt(sizeString);
0616: } catch (NumberFormatException e) {
0617: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0618: throw new InvalidJadException(
0619: InvalidJadException.INVALID_VALUE);
0620: }
0621:
0622: sizeString = state.jadProps
0623: .getProperty(MIDletSuite.DATA_SIZE_PROP);
0624: if (sizeString == null) {
0625: dataSize = 0;
0626: } else {
0627: try {
0628: dataSize = Integer.parseInt(sizeString);
0629: } catch (NumberFormatException e) {
0630: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0631: throw new InvalidJadException(
0632: InvalidJadException.INVALID_VALUE);
0633: }
0634: }
0635:
0636: /*
0637: * A suite is a jad + jar + manifest + url + data size.
0638: * lets say the manifest is the same size as the jad
0639: * since we do know at this point. the size is in bytes,
0640: * UTF-8 chars can be upto 3 bytes
0641: */
0642: suiteSize = info.expectedJarSize + (state.jad.length * 2)
0643: + (info.jadUrl.length() * 3) + dataSize;
0644: state.jad = null;
0645:
0646: state.file = new File();
0647:
0648: if (suiteSize > state.file
0649: .getBytesAvailableForFiles(state.storageId)) {
0650: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0651:
0652: // the size reported to the user should be in K and rounded up
0653: throw new InvalidJadException(
0654: InvalidJadException.INSUFFICIENT_STORAGE, Integer
0655: .toString((suiteSize + 1023) / 1024));
0656: }
0657:
0658: info.jarUrl = state.jadProps
0659: .getProperty(MIDletSuite.JAR_URL_PROP);
0660: if (info.jarUrl == null || info.jarUrl.length() == 0) {
0661: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0662: throw new InvalidJadException(
0663: InvalidJadException.MISSING_JAR_URL);
0664: }
0665:
0666: state.nextStep++;
0667: }
0668:
0669: /**
0670: * Confirm installation with the user.
0671: *
0672: * @exception IOException is thrown, if the user cancels installation
0673: */
0674: private void installStep4() throws IOException {
0675:
0676: synchronized (state) {
0677: /* One more check to see if user has already canceled */
0678: if (state.stopInstallation) {
0679: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0680: throw new IOException("stopped");
0681: }
0682: /*
0683: * Not canceled, so ignore cancel requests for now because below we
0684: * are going to ask anyway if user wants to install suite
0685: */
0686: state.ignoreCancel = true;
0687: }
0688:
0689: if (state.listener != null
0690: && !state.listener.confirmJarDownload(state)) {
0691: state.stopInstallation = true;
0692: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
0693: throw new IOException("stopped");
0694: }
0695:
0696: synchronized (state) {
0697: /* Allow cancel requests again */
0698: state.ignoreCancel = false;
0699: }
0700: state.nextStep++;
0701: }
0702:
0703: /**
0704: * Downloads the JAR, make sure it is the correct size, make sure
0705: * the required attributes match the JAD's. Then store the
0706: * application.
0707: *
0708: * @exception IOException is thrown, if an I/O error occurs during
0709: * descriptor or jar file download
0710: * @exception InvalidJadException is thrown, if the descriptor file is not
0711: * properly formatted or does not contain the required
0712: */
0713: private void installStep5() throws IOException, InvalidJadException {
0714: int bytesDownloaded;
0715: MIDletInfo midletInfo;
0716: String midlet;
0717:
0718: // Send out delete notifications that have been queued, first
0719: OtaNotifier.postQueuedDeleteMsgsBackToProvider(
0720: state.proxyUsername, state.proxyPassword);
0721:
0722: // Save jar file to temp name; we need to do this to read the
0723: // manifest entry, but, don't want to overwrite an existing
0724: // application in case there are problems with the manifest
0725: state.storageRoot = File.getStorageRoot(state.storageId);
0726: info.jarFilename = state.storageRoot + TMP_FILENAME;
0727:
0728: bytesDownloaded = downloadJAR(info.jarFilename);
0729:
0730: if (state.exception != null) {
0731: return;
0732: }
0733:
0734: try {
0735: state.storage = new RandomAccessStream();
0736:
0737: state.installInfo.authPath = verifier.verifyJar(
0738: state.storage, info.jarFilename);
0739:
0740: if (state.listener != null) {
0741: state.listener.updateStatus(VERIFYING_SUITE, state);
0742: }
0743:
0744: // Create JAR Properties (From .jar file's MANIFEST)
0745: try {
0746: state.manifest = JarReader.readJarEntry(
0747: info.jarFilename, MIDletSuite.JAR_MANIFEST);
0748: if (state.manifest == null) {
0749: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0750: throw new InvalidJadException(
0751: InvalidJadException.CORRUPT_JAR,
0752: MIDletSuite.JAR_MANIFEST);
0753: }
0754: } catch (OutOfMemoryError e) {
0755: try {
0756: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0757: } catch (Throwable t) {
0758: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0759: Logging
0760: .report(Logging.WARNING,
0761: LogChannels.LC_AMS,
0762: "Throwable during posting the install message");
0763: }
0764: }
0765:
0766: throw new InvalidJadException(
0767: InvalidJadException.TOO_MANY_PROPS);
0768: } catch (IOException ioe) {
0769: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0770: throw new InvalidJadException(
0771: InvalidJadException.CORRUPT_JAR,
0772: MIDletSuite.JAR_MANIFEST);
0773: }
0774:
0775: state.jarProps = new ManifestProperties();
0776:
0777: try {
0778: state.jarProps.load(new ByteArrayInputStream(
0779: state.manifest));
0780: state.manifest = null;
0781: } catch (OutOfMemoryError e) {
0782: state.manifest = null;
0783: try {
0784: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
0785: } catch (Throwable t) {
0786: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
0787: Logging
0788: .report(Logging.WARNING,
0789: LogChannels.LC_AMS,
0790: "Throwable while posting install message ");
0791: }
0792: }
0793:
0794: throw new InvalidJadException(
0795: InvalidJadException.TOO_MANY_PROPS);
0796: } catch (InvalidJadException ije) {
0797: state.manifest = null;
0798:
0799: try {
0800: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0801: } catch (Throwable t) {
0802: // ignore
0803: }
0804:
0805: throw ije;
0806: }
0807:
0808: for (int i = 1;; i++) {
0809: midlet = state.getAppProperty("MIDlet-" + i);
0810: if (midlet == null) {
0811: break;
0812: }
0813:
0814: /*
0815: * Verify the MIDlet class is present in the JAR
0816: * An exception thrown if not.
0817: * Do the proper install notify on an exception
0818: */
0819: try {
0820: midletInfo = new MIDletInfo(midlet);
0821:
0822: verifyMIDlet(midletInfo.classname);
0823: } catch (InvalidJadException ije) {
0824: if (ije.getReason() == InvalidJadException.INVALID_VALUE) {
0825: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
0826: } else {
0827: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0828: }
0829: throw ije;
0830: }
0831: }
0832:
0833: // move on to the next step after a warning
0834: state.nextStep++;
0835:
0836: // Check Manifest entries against .jad file
0837: if (info.jadUrl != null) {
0838: if (bytesDownloaded != info.expectedJarSize) {
0839: postInstallMsgBackToProvider(OtaNotifier.JAR_SIZE_MISMATCH_MSG);
0840: throw new InvalidJadException(
0841: InvalidJadException.JAR_SIZE_MISMATCH);
0842: }
0843:
0844: if (!info.suiteName.equals(state.jarProps
0845: .getProperty(MIDletSuite.SUITE_NAME_PROP))) {
0846: postInstallMsgBackToProvider(OtaNotifier.ATTRIBUTE_MISMATCH_MSG);
0847: throw new InvalidJadException(
0848: InvalidJadException.SUITE_NAME_MISMATCH);
0849: }
0850:
0851: if (!info.suiteVersion.equals(state.jarProps
0852: .getProperty(MIDletSuite.VERSION_PROP))) {
0853: postInstallMsgBackToProvider(OtaNotifier.ATTRIBUTE_MISMATCH_MSG);
0854: throw new InvalidJadException(
0855: InvalidJadException.VERSION_MISMATCH);
0856: }
0857:
0858: if (!info.suiteVendor.equals(state.jarProps
0859: .getProperty(MIDletSuite.VENDOR_PROP))) {
0860: postInstallMsgBackToProvider(OtaNotifier.ATTRIBUTE_MISMATCH_MSG);
0861: throw new InvalidJadException(
0862: InvalidJadException.VENDOR_MISMATCH);
0863: }
0864: } else {
0865: info.expectedJarSize = bytesDownloaded;
0866: info.suiteName = state.jarProps
0867: .getProperty(MIDletSuite.SUITE_NAME_PROP);
0868: if (info.suiteName == null
0869: || info.suiteName.length() == 0) {
0870: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0871: throw new InvalidJadException(
0872: InvalidJadException.MISSING_SUITE_NAME);
0873: }
0874:
0875: info.suiteVendor = state.jarProps
0876: .getProperty(MIDletSuite.VENDOR_PROP);
0877: if (info.suiteVendor == null
0878: || info.suiteVendor.length() == 0) {
0879: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0880: throw new InvalidJadException(
0881: InvalidJadException.MISSING_VENDOR);
0882: }
0883:
0884: info.suiteVersion = state.jarProps
0885: .getProperty(MIDletSuite.VERSION_PROP);
0886: if (info.suiteVersion == null
0887: || info.suiteVersion.length() == 0) {
0888: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0889: throw new InvalidJadException(
0890: InvalidJadException.MISSING_VERSION);
0891: }
0892:
0893: try {
0894: checkVersionFormat(info.suiteVersion);
0895: } catch (NumberFormatException nfe) {
0896: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
0897: throw new InvalidJadException(
0898: InvalidJadException.INVALID_VERSION);
0899: }
0900:
0901: // if already installed, check the domain of the JAR URL
0902:
0903: info.id = state.midletSuiteStorage.createSuiteID();
0904:
0905: checkPreviousVersion();
0906: }
0907: } catch (Exception e) {
0908: state.file.delete(info.jarFilename);
0909:
0910: if (e instanceof IOException) {
0911: throw (IOException) e;
0912: }
0913:
0914: throw (RuntimeException) e;
0915: }
0916: }
0917:
0918: /**
0919: * If the JAR belongs to an installed suite if there was
0920: * no JAD, check the URL against the installed one.
0921: */
0922: private void installStep6() {
0923: state.nextStep++;
0924:
0925: if (info.jadUrl == null && state.isPreviousVersion) {
0926: checkForDifferentDomains(info.jarUrl);
0927: }
0928: }
0929:
0930: /**
0931: * If some additional (i.e. that are not listed in jad) permissions must
0932: * be allowed, add them to the value of MIDlet-Permissions attribute.
0933: */
0934: private void applyExtraPermissions() {
0935: if (additionalPermissions != null) {
0936: String newPermissions = state.jadProps
0937: .getProperty(MIDletSuite.PERMISSIONS_PROP);
0938:
0939: if (newPermissions != null && newPermissions.length() > 0) {
0940: newPermissions += ",";
0941: }
0942:
0943: if ("all".equals(additionalPermissions)) {
0944: int i;
0945: byte[] domainPermissions = Permissions
0946: .forDomain(info.domain)[Permissions.MAX_LEVELS];
0947:
0948: newPermissions = "";
0949:
0950: for (i = 0; i < Permissions.NUMBER_OF_PERMISSIONS - 1; i++) {
0951: if (domainPermissions[i] != Permissions.NEVER) {
0952: newPermissions += Permissions.getName(i) + ",";
0953: }
0954: }
0955:
0956: // the same for the last permission, but without ","
0957: if (domainPermissions[i] != Permissions.NEVER) {
0958: newPermissions += Permissions.getName(i);
0959: }
0960: } else {
0961: newPermissions += additionalPermissions;
0962: }
0963:
0964: state.jadProps.setProperty(MIDletSuite.PERMISSIONS_PROP,
0965: newPermissions);
0966:
0967: /*
0968: * If the Midlet-Permissions attribute presents in there
0969: * manifest, it must be the same as in jad because the suite
0970: * is trusted.
0971: */
0972: String jarPermissions = state.jarProps
0973: .getProperty(MIDletSuite.PERMISSIONS_PROP);
0974:
0975: if (jarPermissions != null) {
0976: state.jarProps.setProperty(
0977: MIDletSuite.PERMISSIONS_PROP, newPermissions);
0978: }
0979: }
0980: }
0981:
0982: /**
0983: * Checks the permissions and store the suite.
0984: *
0985: * @exception IOException is thrown, if an I/O error occurs during
0986: * storing the suite
0987: * @exception InvalidJadException is thrown, if the there is
0988: * permission problem
0989: */
0990: private void installStep7() throws IOException, InvalidJadException {
0991:
0992: try {
0993: if (info.authPath != null) {
0994: // suite was signed
0995: info.domain = verifier
0996: .getSecurityDomainName(info.authPath[0]);
0997: if (state.listener != null
0998: && !state.listener.confirmAuthPath(state)) {
0999: state.stopInstallation = true;
1000: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
1001: throw new IOException("stopped");
1002: }
1003: } else {
1004: info.domain = unsignedSecurityDomain;
1005: }
1006:
1007: info.trusted = Permissions.isTrusted(info.domain);
1008:
1009: // Do not overwrite trusted suites with untrusted ones
1010: if (!info.trusted && state.isPreviousVersion
1011: && state.previousSuite.isTrusted()) {
1012:
1013: postInstallMsgBackToProvider(OtaNotifier.AUTHORIZATION_FAILURE_MSG);
1014:
1015: /*
1016: * state.previousInstallInfo.authPath can be null in the case
1017: * if the previously installed suite was not signed but its
1018: * domain was set to some trusted one by AutoTester using
1019: * setUnsignedSecurityDomain().
1020: */
1021: throw new InvalidJadException(
1022: InvalidJadException.TRUSTED_OVERWRITE_FAILURE,
1023: state.previousInstallInfo.authPath != null ? state.previousInstallInfo.authPath[0]
1024: : "");
1025: }
1026:
1027: /*
1028: * The unidentified suites do not get checked for requested
1029: * permissions.
1030: */
1031: if (Permissions.UNIDENTIFIED_DOMAIN_BINDING
1032: .equals(info.domain)) {
1033:
1034: settings
1035: .setPermissions((Permissions
1036: .forDomain(info.domain))[Permissions.CUR_LEVELS]);
1037:
1038: /*
1039: * To keep public key management simple, there is only one
1040: * trusted keystore. So it is possible that the CA for
1041: * the suite is untrusted. This may be done on purpose for
1042: * testing. This is OK, but do not confuse the user by saying
1043: * the untrusted suite is authorized, so set the CA name to
1044: * null.
1045: */
1046: info.authPath = null;
1047: } else {
1048: /*
1049: * For identified suites, make sure an properties duplicated in
1050: * both the manifest and JAD are the same.
1051: */
1052: if (info.jadUrl != null) {
1053: checkForJadManifestMismatches();
1054: }
1055:
1056: /*
1057: * This is needed by the AutoTester: sometimes it is required
1058: * to allow some permissions even if they are not listed in jad.
1059: */
1060: applyExtraPermissions();
1061:
1062: settings
1063: .setPermissions(getInitialPermissions(info.domain));
1064: }
1065:
1066: if (state.isPreviousVersion) {
1067: applyCurrentUserLevelPermissions(
1068: state.previousSuite.getPermissions(),
1069: (Permissions.forDomain(info.domain))[Permissions.MAX_LEVELS],
1070: settings.getPermissions());
1071:
1072: if (state.removeRMS) {
1073: // override suite comparisons, just remove RMS
1074: RecordStoreFactory.removeRecordStoresForSuite(null,
1075: info.id);
1076: } else {
1077: processPreviousRMS();
1078: }
1079: }
1080:
1081: state.securityHandler = new SecurityHandler(settings
1082: .getPermissions(), info.domain);
1083:
1084: checkRuntimeEnv();
1085: checkConfiguration();
1086: matchProfile();
1087:
1088: try {
1089: state.chmanager.preInstall(this , (InstallState) state,
1090: (MIDletSuite) state,
1091: (info.authPath == null ? null
1092: : info.authPath[0]));
1093: } catch (InvalidJadException jex) {
1094: // Post the correct install notify msg back to the server
1095: String msg = OtaNotifier.INVALID_CONTENT_HANDLER;
1096: if (jex.getReason() == InvalidJadException.CONTENT_HANDLER_CONFLICT) {
1097: msg = OtaNotifier.CONTENT_HANDLER_CONFLICT;
1098: }
1099:
1100: postInstallMsgBackToProvider(msg);
1101: throw jex;
1102: } catch (SecurityException se) {
1103: postInstallMsgBackToProvider(OtaNotifier.AUTHORIZATION_FAILURE_MSG);
1104:
1105: // since our state object put the permission in message
1106: throw new InvalidJadException(
1107: InvalidJadException.AUTHORIZATION_FAILURE, se
1108: .getMessage());
1109: }
1110:
1111: // make sure at least 1 second has passed
1112: try {
1113: long waitTime = 1000 - (System.currentTimeMillis() - state.startTime);
1114:
1115: if (waitTime > 0 && waitTime <= 1000) {
1116: Thread.sleep(waitTime);
1117: }
1118: } catch (InterruptedException ie) {
1119: // ignore
1120: }
1121:
1122: synchronized (state) {
1123: // this is the point of no return, one last check
1124: if (state.stopInstallation) {
1125: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
1126: throw new IOException("stopped");
1127: }
1128:
1129: state.ignoreCancel = true;
1130: }
1131:
1132: if (state.listener != null) {
1133: state.listener.updateStatus(STORING_SUITE, state);
1134: }
1135:
1136: registerPushConnections();
1137:
1138: /** Do the Content Handler registration updates now */
1139: state.chmanager.install();
1140:
1141: /*
1142: * Store suite will remove the suite including push connections,
1143: * if there an error, but may not remove the temp jar file.
1144: */
1145: MIDletInfo midletInfo = state.getMidletInfo();
1146: String midletClassNameToRun = null, iconName;
1147: MIDletSuiteInfo msi;
1148:
1149: iconName = state.getAppProperty("MIDlet-Icon");
1150: if (iconName != null) {
1151: iconName.trim();
1152: }
1153:
1154: if (midletInfo != null) {
1155: midletClassNameToRun = midletInfo.classname;
1156: if (iconName == null) {
1157: // If an icon for the suite is not specified,
1158: // use the first midlet's icon.
1159: iconName = midletInfo.icon;
1160: }
1161: }
1162:
1163: msi = new MIDletSuiteInfo(info.id);
1164: msi.displayName = state.getDisplayName();
1165: msi.midletToRun = midletClassNameToRun;
1166: msi.numberOfMidlets = state.getNumberOfMIDlets();
1167: /* default is to enable a newly installed suite */
1168: msi.enabled = true;
1169: msi.trusted = info.trusted;
1170: msi.preinstalled = false;
1171: msi.iconName = iconName;
1172: msi.storageId = state.storageId;
1173:
1174: state.midletSuiteStorage.storeSuite(info, settings, msi,
1175: state.jadProps, state.jarProps);
1176: } catch (Throwable e) {
1177: state.file.delete(info.jarFilename);
1178: if (e instanceof IOException) {
1179: throw (IOException) e;
1180: }
1181:
1182: if (e instanceof OutOfMemoryError) {
1183: try {
1184: postInstallMsgBackToProvider(OtaNotifier.INSUFFICIENT_MEM_MSG);
1185: } catch (Throwable t) {
1186: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
1187: Logging
1188: .report(Logging.WARNING,
1189: LogChannels.LC_AMS,
1190: "Throwable during posting install message");
1191: }
1192: }
1193:
1194: throw new InvalidJadException(
1195: InvalidJadException.TOO_MANY_PROPS);
1196: }
1197:
1198: throw (RuntimeException) e;
1199: }
1200:
1201: state.nextStep++;
1202:
1203: try {
1204: postInstallMsgBackToProvider(OtaNotifier.SUCCESS_MSG);
1205: } catch (Throwable t) {
1206: /*
1207: * The suite is successfully installed, but the post of the
1208: * status message failed. Do not let this failure prevent
1209: * the suite from being used.
1210: */
1211: }
1212: }
1213:
1214: /**
1215: * Installation time optimizations are to be done in this step.
1216: * MONET optimization, VERIFY ONCE optimization, etc.
1217: * This step is done after obligatory installation part,
1218: * so the suite is downloaded, checked and stored by this moment.
1219: */
1220: private void installStep8() throws IOException {
1221:
1222: // In case of installation from JAR the suite id as well as other
1223: // properties are read from .jar file's MANIFEST rather then from
1224: // JAD file. Only after that application image can be generated.
1225: if (Constants.MONET_ENABLED) {
1226: if (state.listener != null) {
1227: state.listener
1228: .updateStatus(GENERATING_APP_IMAGE, state);
1229: }
1230: MIDletAppImageGenerator.createAppImage(info.id,
1231: state.midletSuiteStorage);
1232: }
1233: // Verification caching should be done if MONET disabled only
1234: else if (Constants.VERIFY_ONCE) {
1235: if (state.listener != null) {
1236: state.listener.updateStatus(VERIFYING_SUITE_CLASSES,
1237: state);
1238: }
1239: // Preverify all suite classes
1240: // in the case of success store hash value of the suite
1241: try {
1242: info.verifyHash = MIDletSuiteVerifier
1243: .verifySuiteClasses(info.id,
1244: state.midletSuiteStorage);
1245: if (info.verifyHash != null) {
1246: state.midletSuiteStorage.storeSuiteVerifyHash(
1247: info.id, info.verifyHash);
1248: }
1249: } catch (Throwable t) {
1250: // Notify installation listener of verifcation error
1251: state.exception = new InvalidJadException(
1252: InvalidJadException.JAR_CLASSES_VERIFICATION_FAILED);
1253: if (state.listener != null) {
1254: state.listener.updateStatus(
1255: VERIFYING_SUITE_CLASSES, state);
1256: }
1257:
1258: // Clean exception since this step is optional and its
1259: // problems shouldn't cause whole installation failure
1260: state.exception = null;
1261: }
1262: }
1263:
1264: state.nextStep++;
1265: }
1266:
1267: /**
1268: * Verify that a class is present in the JAR file.
1269: * If the classname is invalid or is not found an
1270: * InvalidJadException is thrown.
1271: * @param classname the name of the class to verify
1272: * @exception InvalidJadException is thrown if the name is null or empty
1273: * or if the file is not found
1274: */
1275: public void verifyMIDlet(String classname)
1276: throws InvalidJadException {
1277: if (classname == null || classname.length() == 0) {
1278: throw new InvalidJadException(
1279: InvalidJadException.INVALID_VALUE);
1280: }
1281:
1282: String file = classname.replace('.', '/').concat(".class");
1283:
1284: try {
1285: /* Attempt to read the MIDlet from the JAR file. */
1286: if (JarReader.readJarEntry(info.jarFilename, file) != null) {
1287: return; // File found, normal return
1288: }
1289: // Fall into throwing the exception
1290: } catch (IOException ioe) {
1291: // Fall into throwing the exception
1292: }
1293: // Throw the InvalidJadException
1294: throw new InvalidJadException(InvalidJadException.CORRUPT_JAR,
1295: file);
1296: }
1297:
1298: /**
1299: * Downloads an application descriptor file from the given URL.
1300: *
1301: * @return a byte array representation of the file or null if not found
1302: *
1303: * @exception IOException is thrown if any error prevents the download
1304: * of the JAD
1305: */
1306: protected abstract byte[] downloadJAD() throws IOException;
1307:
1308: /**
1309: * Downloads an application archive file from the given URL into the
1310: * given file. Automatically handle re-tries.
1311: *
1312: * @param filename name of the file to write. This file resides
1313: * in the storage area of the given application
1314: *
1315: * @return size of the JAR
1316: *
1317: * @exception IOException is thrown if any error prevents the download
1318: * of the JAR
1319: */
1320: protected abstract int downloadJAR(String filename)
1321: throws IOException;
1322:
1323: /**
1324: * If the JAD belongs to an installed suite, check the URL against the
1325: * installed one. Set the state.exception if the user needs to be warned.
1326: *
1327: * @param url JAD or JAR URL of the suite being installed
1328: */
1329: protected void checkForDifferentDomains(String url) {
1330: String previousUrl = state.previousInstallInfo.getDownloadUrl();
1331: // perform a domain check not a straight compare
1332: if (info.authPath == null && previousUrl != null) {
1333: HttpUrl old = new HttpUrl(previousUrl);
1334: HttpUrl current = new HttpUrl(url);
1335:
1336: if ((current.domain != null && old.domain == null)
1337: || (current.domain == null && old.domain != null)
1338: || (current.domain != null && old.domain != null && !current.domain
1339: .regionMatches(true, 0, old.domain, 0,
1340: old.domain.length()))) {
1341: /*
1342: * The jad is at new location, could be bad,
1343: * let the user decide
1344: */
1345: state.exception = new InvalidJadException(
1346: InvalidJadException.JAD_MOVED, previousUrl);
1347: return;
1348: }
1349: }
1350: }
1351:
1352: /**
1353: * See if there is an installed version of the suite being installed and
1354: * if so, make an necessary checks. Will set state fields, including
1355: * the exception field for warning the user.
1356: *
1357: * @exception InvalidJadException if the new version is formated
1358: * incorrectly
1359: * @exception MIDletSuiteLockedException is thrown, if the MIDletSuite is
1360: * locked
1361: */
1362: protected void checkPreviousVersion() throws InvalidJadException,
1363: MIDletSuiteLockedException {
1364:
1365: int id;
1366: MIDletSuiteImpl midletSuite;
1367: String installedVersion;
1368: int cmpResult;
1369:
1370: // Check if app already exists
1371: id = MIDletSuiteStorage.getSuiteID(info.suiteVendor,
1372: info.suiteName);
1373: if (id == MIDletSuite.UNUSED_SUITE_ID) {
1374: // there is no previous version
1375: return;
1376: }
1377:
1378: try {
1379: midletSuite = state.midletSuiteStorage.getMIDletSuite(id,
1380: true);
1381:
1382: if (midletSuite == null) {
1383: // there is no previous version
1384: return;
1385: }
1386: checkVersionFormat(info.suiteVersion);
1387:
1388: state.isPreviousVersion = true;
1389:
1390: // This is now an update, use the old ID
1391: info.id = id;
1392:
1393: state.previousSuite = midletSuite;
1394: state.previousInstallInfo = midletSuite.getInstallInfo();
1395:
1396: if (state.force) {
1397: // do not ask questions, force an overwrite
1398: return;
1399: }
1400:
1401: // If it does, check version information
1402: installedVersion = midletSuite
1403: .getProperty(MIDletSuite.VERSION_PROP);
1404: cmpResult = vercmp(info.suiteVersion, installedVersion);
1405: if (cmpResult < 0) {
1406: // older version, warn user
1407: state.exception = new InvalidJadException(
1408: InvalidJadException.OLD_VERSION,
1409: installedVersion);
1410: return;
1411: }
1412:
1413: if (cmpResult == 0) {
1414: // already installed, warn user
1415: state.exception = new InvalidJadException(
1416: InvalidJadException.ALREADY_INSTALLED,
1417: installedVersion);
1418: return;
1419: }
1420:
1421: // new version, warn user
1422: state.exception = new InvalidJadException(
1423: InvalidJadException.NEW_VERSION, installedVersion);
1424: return;
1425: } catch (MIDletSuiteCorruptedException mce) {
1426: if (state.listener != null) {
1427: state.listener.updateStatus(CORRUPTED_SUITE, state);
1428: }
1429: } catch (NumberFormatException nfe) {
1430: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
1431: throw new InvalidJadException(
1432: InvalidJadException.INVALID_VERSION);
1433: }
1434: }
1435:
1436: /**
1437: * Posts a status message back to the provider's URL in JAD.
1438: *
1439: * @param message status message to post
1440: */
1441: protected void postInstallMsgBackToProvider(String message) {
1442: OtaNotifier.postInstallMsgBackToProvider(message, state,
1443: state.proxyUsername, state.proxyPassword);
1444: }
1445:
1446: /**
1447: * Function that actually does the work of transferring file data.
1448: * <p>
1449: * Updates the listener every 1 K bytes.
1450: * <p>
1451: * If the amount of data to be read is larger than <code>maxDLSize</code>
1452: * we will break the input into chunks no larger than
1453: * <code>chunkSize</code>. This prevents the VM from running out of
1454: * memory when processing large files.
1455: *
1456: * @param in the input stream to read from
1457: * @param out the output stream to write to
1458: * @param chunkSize size of piece to read from the input buffer
1459: *
1460: * @return number of bytes written to the output stream
1461: *
1462: * @exception IOException if any exceptions occur during transfer
1463: * of data
1464: */
1465: protected int transferData(InputStream in, OutputStream out,
1466: int chunkSize) throws IOException {
1467: byte[] buffer = new byte[chunkSize];
1468: int bytesRead;
1469: int totalBytesWritten = 0;
1470:
1471: if (state.listener != null) {
1472: state.listener.updateStatus(state.beginTransferDataStatus,
1473: state);
1474: }
1475:
1476: try {
1477: for (int nextUpdate = totalBytesWritten + 1024;;) {
1478: bytesRead = in.read(buffer);
1479:
1480: if (state.listener != null
1481: && (bytesRead == -1 || totalBytesWritten
1482: + bytesRead >= nextUpdate)) {
1483:
1484: synchronized (state) {
1485: if (state.stopInstallation) {
1486: throw new IOException("stopped");
1487: }
1488:
1489: state.listener.updateStatus(
1490: state.transferStatus, state);
1491: }
1492:
1493: nextUpdate = totalBytesWritten + 1024;
1494: }
1495:
1496: if (bytesRead == -1) {
1497: return totalBytesWritten;
1498: }
1499:
1500: out.write(buffer, 0, bytesRead);
1501: totalBytesWritten += bytesRead;
1502: }
1503: } catch (IOException ioe) {
1504: if (state.stopInstallation) {
1505: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
1506: throw new IOException("stopped");
1507: } else {
1508: throw ioe;
1509: }
1510: }
1511: }
1512:
1513: /**
1514: * Retrieves a scheme component of the given URL.
1515: *
1516: * @param url url to parse
1517: * @param defaultScheme if the url has no scheme component, this one
1518: * will be returned; may be null
1519: *
1520: * @return scheme component of the given URL
1521: */
1522: public static String getUrlScheme(String url, String defaultScheme) {
1523: if (url == null) {
1524: return null;
1525: }
1526:
1527: /* this will parse any kind of URL, not only Http */
1528: HttpUrl parsedUrl = new HttpUrl(url);
1529:
1530: if (parsedUrl.scheme == null) {
1531: return defaultScheme;
1532: }
1533:
1534: return parsedUrl.scheme;
1535: }
1536:
1537: /**
1538: * Retrieves a path component of the given URL.
1539: *
1540: * @param url url to parse
1541: *
1542: * @return path component of the given URL
1543: */
1544: public static String getUrlPath(String url) {
1545: if (url == null) {
1546: return null;
1547: }
1548:
1549: /* this will parse any kind of URL, not only Http */
1550: HttpUrl parsedUrl = new HttpUrl(url);
1551: String path = parsedUrl.path;
1552:
1553: /*
1554: IMPL_NOTE: In current implementation of HttpUrl
1555: the absolute path always begins with '/' which
1556: would make getUrlPath() produce the win32
1557: paths in the form "/C:/path/to/file" that is
1558: rejected by the filesystem.
1559: The initial '/' in 'path' is currently the only
1560: flag which allows to distinguish between absolute
1561: and relative url.
1562: Probably there should be a special flag in HttpUrl
1563: to distinguish between absolute and relative urls.
1564: Moreover it seems necessary to have platform-dependent
1565: conversion procedure from url path to filesystem path.
1566: */
1567:
1568: if (path != null) {
1569: if (path.charAt(0) == '/') {
1570: path = path.substring(1, path.length());
1571: }
1572: }
1573:
1574: return path;
1575: }
1576:
1577: /**
1578: * Compares two URLs for equality in sense that they have the same
1579: * scheme, host and path.
1580: *
1581: * @param url1 the first URL for comparision
1582: * @param url1 the second URL for comparision
1583: *
1584: * @return true if the scheme, host and path of the first given url
1585: * is identical to the scheme, host and path of the second
1586: * given url; false otherwise
1587: */
1588: protected abstract boolean isSameUrl(String url1, String url2);
1589:
1590: /**
1591: * If this is an update, make sure the RMS data is handle correctly
1592: * according to the OTA spec.
1593: * <p>
1594: * From the OTA spec:
1595: * <blockquote>
1596: * The RMS record stores of a MIDlet suite being updated MUST be
1597: * managed as follows:</p>
1598: * <ul>
1599: * <li>
1600: * If the cryptographic signer of the new MIDlet suite and the
1601: * original MIDlet suite are identical, then the RMS record
1602: * stores MUST be retained and made available to the new MIDlet
1603: * suite.</li>
1604: * <li>
1605: * If the scheme, host, and path of the URL that the new
1606: * Application Descriptor is downloaded from is identical to the
1607: * scheme, host, and path of the URL the original Application
1608: * Descriptor was downloaded from, then the RMS MUST be retained
1609: * and made available to the new MIDlet suite.</li>
1610: * <li>
1611: * If the scheme, host, and path of the URL that the new MIDlet
1612: * suite is downloaded from is identical to the scheme, host, and
1613: * path of the URL the original MIDlet suite was downloaded from,
1614: * then the RMS MUST be retained and made available to the new
1615: * MIDlet suite.</li>
1616: * <li>
1617: * If the above statements are false, then the device MUST ask
1618: * the user whether the data from the original MIDlet suite
1619: * should be retained and made available to the new MIDlet
1620: * suite.</li>
1621: * </ul>
1622: * </blockquote>
1623: *
1624: * @exception IOException if the install is stopped
1625: */
1626: protected void processPreviousRMS() throws IOException {
1627: if (!RecordStoreFactory.suiteHasRmsData(info.id)) {
1628: return;
1629: }
1630:
1631: if (state.previousInstallInfo.authPath != null
1632: && info.authPath != null
1633: && info.authPath[0]
1634: .equals(state.previousInstallInfo.authPath[0])) {
1635: // signers the same
1636: return;
1637: }
1638:
1639: if (isSameUrl(info.jadUrl, state.previousInstallInfo
1640: .getJadUrl())
1641: || isSameUrl(info.jarUrl, state.previousInstallInfo
1642: .getJarUrl())) {
1643: return;
1644: }
1645:
1646: // ask the user, if no listener assume no for user's answer
1647: if (state.listener != null) {
1648: if (state.listener.keepRMS(state)) {
1649: // user wants to keep the data
1650: return;
1651: }
1652: }
1653:
1654: // this is a good place to check for a stop installing call
1655: if (state.stopInstallation) {
1656: postInstallMsgBackToProvider(OtaNotifier.USER_CANCELLED_MSG);
1657: throw new IOException("stopped");
1658: }
1659:
1660: RecordStoreFactory.removeRecordStoresForSuite(null, info.id);
1661: }
1662:
1663: /**
1664: * Stops the installation. If installer is not installing then this
1665: * method has no effect. This will cause the install method to
1666: * throw an IOException if the install is not writing the suite
1667: * to storage which is the point of no return.
1668: *
1669: * @return true if the install will stop, false if it is too late
1670: */
1671: public boolean stopInstalling() {
1672: if (state == null) {
1673: return false;
1674: }
1675:
1676: synchronized (state) {
1677: if (state.ignoreCancel) {
1678: return false;
1679: }
1680:
1681: state.stopInstallation = true;
1682: }
1683:
1684: return true;
1685: }
1686:
1687: /**
1688: * Tells if the installation was stopped by another thread.
1689: * @return true if the installation was stopped by another thread
1690: */
1691: public boolean wasStopped() {
1692: if (state == null) {
1693: return false;
1694: }
1695:
1696: return state.stopInstallation;
1697: }
1698:
1699: /**
1700: * Builds the initial API permission for suite currently being installed.
1701: *
1702: * @param domain security domain name for the CA of the suite
1703: *
1704: * @return current level of permissions
1705: *
1706: * @exception InvalidJadException if a permission attribute is not
1707: * formatted properly or a required permission is denied
1708: */
1709: protected byte[] getInitialPermissions(String domain)
1710: throws InvalidJadException {
1711: byte[][] domainPermissions = Permissions.forDomain(domain);
1712: byte[] permissions = Permissions.getEmptySet();
1713:
1714: // only the current level of each permission has to be adjusted
1715: getRequestedPermissions(MIDletSuite.PERMISSIONS_PROP,
1716: domainPermissions[Permissions.CUR_LEVELS], permissions,
1717: true);
1718:
1719: getRequestedPermissions(MIDletSuite.PERMISSIONS_OPT_PROP,
1720: domainPermissions[Permissions.CUR_LEVELS], permissions,
1721: false);
1722:
1723: return permissions;
1724: }
1725:
1726: /**
1727: * Gets the permissions for a domain that are requested the manifest.
1728: *
1729: * @param propName name of the property in the manifest
1730: * @param domainPermissions array of the starting levels for permissions
1731: * of a domain
1732: * @param permissions array to put the permissions from the domain in
1733: * when found in the manifest property
1734: * @param required if set to true the manifest permissions are required
1735: *
1736: * @exception InvalidJadException if a permission attribute is not
1737: * formatted properly or a required permission is denied
1738: */
1739: private void getRequestedPermissions(String propName,
1740: byte[] domainPermissions, byte[] permissions,
1741: boolean required) throws InvalidJadException {
1742:
1743: String jadPermissionLine;
1744: String reqPermissionLine;
1745: Vector reqPermissions;
1746: String permission;
1747: boolean found;
1748: int i;
1749:
1750: reqPermissionLine = state.getAppProperty(propName);
1751: if (reqPermissionLine == null
1752: || reqPermissionLine.length() == 0) {
1753: // Zero properties are allowed.
1754: return;
1755: }
1756:
1757: reqPermissions = Util
1758: .getCommaSeparatedValues(reqPermissionLine);
1759: if (reqPermissions.size() == 0) {
1760: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
1761: throw new InvalidJadException(
1762: InvalidJadException.INVALID_VALUE);
1763: }
1764:
1765: for (int j = 0; j < reqPermissions.size(); j++) {
1766: permission = (String) reqPermissions.elementAt(j);
1767:
1768: if (permission.length() == 0) {
1769: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAD_MSG);
1770: throw new InvalidJadException(
1771: InvalidJadException.INVALID_VALUE);
1772: }
1773:
1774: found = false;
1775: for (i = 0; i < Permissions.NUMBER_OF_PERMISSIONS; i++) {
1776: if (Permissions.getName(i).equals(permission)) {
1777: if (domainPermissions[i] != Permissions.NEVER) {
1778: found = true;
1779: }
1780:
1781: break;
1782: }
1783: }
1784:
1785: if (!found) {
1786: if (required) {
1787: postInstallMsgBackToProvider(OtaNotifier.AUTHORIZATION_FAILURE_MSG);
1788: throw new InvalidJadException(
1789: InvalidJadException.AUTHORIZATION_FAILURE,
1790: permission);
1791: }
1792:
1793: continue;
1794: }
1795:
1796: permissions[i] = domainPermissions[i];
1797: }
1798: }
1799:
1800: /**
1801: * Apply the previous user level permission of the currently installed
1802: * version of a suite to the next version of the suite in a secure way.
1803: *
1804: * @param current array permissions for the current version
1805: * @param domainPermissions array of the starting levels for permissions
1806: * of the new domain
1807: * @param next array permissions for the next version
1808: */
1809: private void applyCurrentUserLevelPermissions(byte[] current,
1810: byte[] domainPermissions, byte[] next) {
1811:
1812: for (int i = 0; i < current.length && i < next.length; i++) {
1813: switch (current[i]) {
1814: case Permissions.ALLOW:
1815: case Permissions.NEVER:
1816: // not a user level permission
1817: continue;
1818: }
1819:
1820: switch (domainPermissions[i]) {
1821: case Permissions.ALLOW:
1822: case Permissions.NEVER:
1823: // not a user level permission
1824: continue;
1825:
1826: case Permissions.ONESHOT:
1827: if (current[i] == Permissions.SESSION) {
1828: // do not apply
1829: continue;
1830: }
1831: // fall through; per-session permissions may be permitted.
1832:
1833: case Permissions.SESSION:
1834: if (current[i] == Permissions.BLANKET
1835: || current[i] == Permissions.BLANKET_GRANTED) {
1836: // do not apply
1837: continue;
1838: }
1839: // fall through to store the permission for the next version.
1840:
1841: default:
1842: next[i] = current[i];
1843: continue;
1844: }
1845: }
1846: }
1847:
1848: /**
1849: * Checks to see if the JAD has a signature, but does not verify the
1850: * signature. This is a place holder the the Secure Installer and
1851: * just returns false.
1852: *
1853: * @return true if the JAD has a signature
1854: */
1855: public boolean isJadSigned() {
1856: return verifier.isJadSigned();
1857: }
1858:
1859: /**
1860: * Checks if the calling suite has Permissions.MIDP permission.
1861: * If not, the SecurityException is thrown.
1862: */
1863: private void checkMidpPermission() {
1864: MIDletSuite midletSuite = MIDletStateHandler
1865: .getMidletStateHandler().getMIDletSuite();
1866:
1867: // if a MIDlet suite is not started, assume the JAM is calling.
1868: if (midletSuite != null) {
1869: midletSuite.checkIfPermissionAllowed(Permissions.MIDP);
1870: }
1871: }
1872:
1873: /**
1874: * Sets security domain for unsigned suites. The default is untrusted.
1875: * Can only be called by JAM for testing.
1876: *
1877: * @param domain name of a security domain
1878: * @param allowedPermissions list of permissions that must be allowed even
1879: * if they are absent from the jad file; "all" to allow all permissions
1880: */
1881: public void setUnsignedSecurityDomain(String domain) {
1882: checkMidpPermission();
1883: unsignedSecurityDomain = domain;
1884: }
1885:
1886: /**
1887: * Sets the permissions that must be allowed not depending on their
1888: * presence in the application descriptor file.
1889: * Can only be called by JAM for testing.
1890: *
1891: * @param extraPermissions list of permissions that must be allowed even
1892: * if they are absent from the jad file; "all" to allow all permissions
1893: */
1894: public void setExtraPermissions(String extraPermissions) {
1895: checkMidpPermission();
1896: additionalPermissions = extraPermissions;
1897: }
1898:
1899: /**
1900: * Checks to see that if any properties that are both in the JAD and
1901: * JAR manifest are not equal and throw a exception and notify the
1902: * server when a mismatch is found. Only used for trusted suites.
1903: * @exception InvalidJadException if the properties do not match
1904: */
1905: protected void checkForJadManifestMismatches()
1906: throws InvalidJadException {
1907:
1908: for (int i = 0; i < state.jarProps.size(); i++) {
1909: String key = state.jarProps.getKeyAt(i);
1910: String value = state.jarProps.getValueAt(i);
1911: String dup = state.jadProps.getProperty(key);
1912:
1913: if (dup == null) {
1914: continue;
1915: }
1916:
1917: if (!dup.equals(value)) {
1918: postInstallMsgBackToProvider(OtaNotifier.ATTRIBUTE_MISMATCH_MSG);
1919: throw new InvalidJadException(
1920: InvalidJadException.ATTRIBUTE_MISMATCH, key);
1921: }
1922: }
1923: }
1924:
1925: /**
1926: * Compares two version strings. The return values are very similar to
1927: * that of strcmp() in 'C'. If the first version is less than the second
1928: * version, a negative number will be returned. If the first version is
1929: * greater than the second version, a positive number will be returned.
1930: * If the two versions are equal, zero is returned.
1931: * <p>
1932: * Versions must be in the form <em>xxx.yyy.zzz</em>, where:
1933: * <pre>
1934: * <em>xxx</em> is the major version
1935: * <em>yyy</em> is the minor version
1936: * <em>zzz</em> is the micro version
1937: * </pre>
1938: * It is acceptable to omit the micro and possibly the minor versions.
1939: * If these are not included in the version string, the period immediately
1940: * preceding the number must also be removed. So, the versions
1941: * <em>xxx.yyy</em> or <em>xxx</em> are also valid.
1942: * <p>
1943: * Version numbers do not have to be three digits wide. However, you may
1944: * pad versions with leading zeros if desired.
1945: * <p>
1946: * If a version number is omitted, its value is assumed to be zero. All
1947: * tests will be based on this assumption.
1948: * <p>
1949: * For example:
1950: * <pre>
1951: * 1.04 > 1.
1952: * 1.04 < 1.4.1
1953: * 1.04 = 1.4.0
1954: * </pre>
1955: * <p>
1956: *
1957: * @param ver1 the first version to compare
1958: * @param ver2 the second version to compare
1959: *
1960: * @return 1 if <code>ver1</code> is greater than <code>ver2</code>
1961: * 0 if <code>ver1</code> is equal to <code>ver2</code>
1962: * -1 if <code>ver1</code> is less than <code>ver2</code>
1963: *
1964: * @exception NumberFormatException if either <code>ver1</code> or
1965: * <code>ver2</code> contain characters that are not numbers or periods
1966: */
1967: private static int vercmp(String ver1, String ver2)
1968: throws NumberFormatException {
1969: String strVal1;
1970: String strVal2;
1971: int intVal1;
1972: int intVal2;
1973: int idx1 = 0;
1974: int idx2 = 0;
1975: int newidx;
1976:
1977: if ((ver1 == null) && (ver2 == null)) {
1978: return 0;
1979: }
1980:
1981: if (ver1 == null) {
1982: return -1;
1983: }
1984:
1985: if (ver2 == null) {
1986: return 1;
1987: }
1988:
1989: for (int i = 0; i < 3; i++) {
1990: strVal1 = "0"; // Default value
1991: strVal2 = "0"; // Default value
1992: if (idx1 >= 0) {
1993: newidx = ver1.indexOf('.', idx1);
1994: if (newidx < 0) {
1995: strVal1 = ver1.substring(idx1);
1996: } else {
1997: strVal1 = ver1.substring(idx1, newidx);
1998: newidx++; // Idx of '.'; need to go to next char
1999: }
2000:
2001: idx1 = newidx;
2002: }
2003:
2004: if (idx2 >= 0) {
2005: newidx = ver2.indexOf('.', idx2);
2006: if (newidx < 0) {
2007: strVal2 = ver2.substring(idx2);
2008: } else {
2009: strVal2 = ver2.substring(idx2, newidx);
2010: newidx++;
2011: }
2012:
2013: idx2 = newidx;
2014: }
2015:
2016: intVal1 = Integer.parseInt(strVal1); // May throw NFE
2017: intVal2 = Integer.parseInt(strVal2); // May throw NFE
2018:
2019: if (intVal1 > intVal2) {
2020: return 1;
2021: }
2022:
2023: if (intVal1 < intVal2) {
2024: return -1;
2025: }
2026: }
2027:
2028: return 0;
2029: }
2030:
2031: /**
2032: * Checks the format of a version string.
2033: * <p>
2034: * Versions must be in the form <em>xxx.yyy.zzz</em>, where:
2035: * <pre>
2036: * <em>xxx</em> is the major version
2037: * <em>yyy</em> is the minor version
2038: * <em>zzz</em> is the micro version
2039: * </pre>
2040: * It is acceptable to omit the micro and possibly the minor versions.
2041: * If these are not included in the version string, the period immediately
2042: * preceding the number must also be removed. So, the versions
2043: * <em>xxx.yyy</em> or <em>xxx</em> are also valid.
2044: * <p>
2045: * Version numbers do not have to be three digits wide. However, you may
2046: * pad versions with leading zeros if desired.
2047: *
2048: * @param ver the version to check
2049: *
2050: * @exception NumberFormatException if <code>ver</code>
2051: * contains any characters that are not numbers or periods
2052: */
2053: private static void checkVersionFormat(String ver)
2054: throws NumberFormatException {
2055: int length;
2056: int start = 0;
2057: int end;
2058:
2059: length = ver.length();
2060: for (int i = 0;; i++) {
2061: // check for more than 3 parts or a trailing '.'
2062: if (i == 3 || start == length) {
2063: throw new NumberFormatException();
2064: }
2065:
2066: end = ver.indexOf('.', start);
2067: if (end == -1) {
2068: end = length;
2069: }
2070:
2071: // throws NFE if the substring is not all digits
2072: Integer.parseInt(ver.substring(start, end));
2073:
2074: if (end == length) {
2075: // we are done
2076: return;
2077: }
2078:
2079: // next time around start after the index of '.'
2080: start = end + 1;
2081: }
2082: }
2083:
2084: /**
2085: * Checks to make sure the runtime environment required by
2086: * the application is supported.
2087: * Send a message back to the server if the check fails and
2088: * throw an exception.
2089: *
2090: * @exception InvalidJadException if the check fails
2091: */
2092: private void checkRuntimeEnv() throws InvalidJadException {
2093: String execEnv;
2094:
2095: execEnv = state
2096: .getAppProperty(MIDletSuite.RUNTIME_EXEC_ENV_PROP);
2097: if (execEnv == null || execEnv.length() == 0) {
2098: execEnv = MIDletSuite.RUNTIME_EXEC_ENV_DEFAULT;
2099: }
2100:
2101: // need to call trim to remove trailing spaces
2102: execEnv = execEnv.trim();
2103:
2104: if (execEnv.equals(cldcRuntimeEnv)) {
2105: // success, done
2106: return;
2107: }
2108:
2109: postInstallMsgBackToProvider(OtaNotifier.INCOMPATIBLE_MSG);
2110: throw new InvalidJadException(
2111: InvalidJadException.DEVICE_INCOMPATIBLE);
2112: }
2113:
2114: /**
2115: * Match the name of the configuration or profile, and return
2116: * true if the first name has a greater or equal version than the
2117: * second. The names of the format "XXX-Y.Y" (e.g. CLDC-1.0, MIDP-2.0)
2118: * as used in the system properties (microedition.configuration &
2119: * microedition.profiles).
2120: *
2121: * This is used for checking both configuration and profiles.
2122: *
2123: * @param name1 name of configuration or profile
2124: * @param name2 name of configuration or profile
2125: * @return true is name1 matches name2 and is greater or equal in
2126: * version number. false otherwise
2127: */
2128: private static boolean matchVersion(String name1, String name2) {
2129: int dash1 = name1.indexOf('-');
2130:
2131: if (dash1 < 0) {
2132: return false;
2133: }
2134:
2135: int dash2 = name2.indexOf('-');
2136:
2137: if (dash2 < 0) {
2138: return false;
2139: }
2140:
2141: String base1 = name1.substring(0, dash1);
2142: String base2 = name2.substring(0, dash2);
2143:
2144: if (!base1.equals(base2)) {
2145: return false;
2146: }
2147:
2148: String ver1 = name1.substring(dash1 + 1, name1.length());
2149: String ver2 = name2.substring(dash2 + 1, name2.length());
2150:
2151: return (vercmp(ver1, ver2) >= 0);
2152: }
2153:
2154: /**
2155: * Checks to make sure the configration need by the application
2156: * is supported.
2157: * Send a message back to the server if the check fails and
2158: * throw an exception.
2159: *
2160: * @exception InvalidJadException if the check fails
2161: */
2162: private void checkConfiguration() throws InvalidJadException {
2163: String config;
2164:
2165: config = state.getAppProperty(MIDletSuite.CONFIGURATION_PROP);
2166: if (config == null || config.length() == 0) {
2167: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
2168: throw new InvalidJadException(
2169: InvalidJadException.MISSING_CONFIGURATION);
2170: }
2171:
2172: if (cldcConfig == null) {
2173: // need to call trim to remove trailing spaces
2174: cldcConfig = System.getProperty(MICROEDITION_CONFIG).trim();
2175: }
2176:
2177: if (matchVersion(cldcConfig, config)) {
2178: // success, done
2179: return;
2180: }
2181:
2182: postInstallMsgBackToProvider(OtaNotifier.INCOMPATIBLE_MSG);
2183: throw new InvalidJadException(
2184: InvalidJadException.DEVICE_INCOMPATIBLE);
2185: }
2186:
2187: /**
2188: * Tries to match one of the supported profiles with a profile
2189: * listed in string of profiles separated by a space.
2190: * Send a message back to the server if a match is not found and
2191: * throw an exception.
2192: *
2193: * @exception InvalidJadException if there is no match
2194: */
2195: private void matchProfile() throws InvalidJadException {
2196: String profiles = state
2197: .getAppProperty(MIDletSuite.PROFILE_PROP);
2198:
2199: if (profiles == null || profiles.length() == 0) {
2200: postInstallMsgBackToProvider(OtaNotifier.INVALID_JAR_MSG);
2201: throw new InvalidJadException(
2202: InvalidJadException.MISSING_PROFILE);
2203: }
2204:
2205: // build the list of supported profiles if needed
2206: if (supportedProfiles == null) {
2207: int start;
2208: int nextSpace = -1;
2209: String meProfiles = System
2210: .getProperty(MICROEDITION_PROFILES);
2211: if (meProfiles == null || meProfiles.length() == 0) {
2212: throw new RuntimeException(
2213: "system property microedition.profiles not set");
2214: }
2215: supportedProfiles = new Vector();
2216: // need to call trim to remove trailing spaces
2217: meProfiles = meProfiles.trim();
2218:
2219: for (;;) {
2220: start = nextSpace + 1;
2221: nextSpace = meProfiles.indexOf(' ', start);
2222:
2223: // consecutive spaces, keep searching
2224: if (nextSpace == start) {
2225: continue;
2226: }
2227:
2228: if ((nextSpace < 0)) {
2229: supportedProfiles.addElement(meProfiles.substring(
2230: start, meProfiles.length()));
2231: break;
2232: }
2233:
2234: supportedProfiles.addElement(meProfiles.substring(
2235: start, nextSpace));
2236:
2237: }
2238: }
2239:
2240: /*
2241: * for each profiles listed in MicroEdition-Profile, we need to
2242: * find a matching profile in microedition.profiles.
2243: */
2244: int current = 0;
2245: int nextSeparatorIndex = 0;
2246: String requestedProfile;
2247: boolean supported = false;
2248:
2249: // convert tab to space so that the parsing later is simplified
2250: StringBuffer tmp = new StringBuffer(profiles);
2251: boolean modified = false;
2252: while ((nextSeparatorIndex = profiles.indexOf('\t', current)) != -1) {
2253: tmp.setCharAt(nextSeparatorIndex, ' ');
2254: current++;
2255: modified = true;
2256: }
2257:
2258: if (modified) {
2259: profiles = tmp.toString();
2260: }
2261:
2262: // reset the indices
2263: current = nextSeparatorIndex = 0;
2264: do {
2265: // get the next requested profiles
2266: nextSeparatorIndex = profiles.indexOf(' ', current);
2267:
2268: if (nextSeparatorIndex == current) {
2269: // consecutive spaces, keep searching
2270: current++;
2271: continue;
2272: }
2273:
2274: if (nextSeparatorIndex == -1) {
2275: // last (or the only one) value in the list
2276: requestedProfile = profiles.substring(current, profiles
2277: .length());
2278: } else {
2279: requestedProfile = profiles.substring(current,
2280: nextSeparatorIndex);
2281: current = nextSeparatorIndex + 1;
2282: }
2283:
2284: /*
2285: * try to match each requested profiles against the supported
2286: * ones.
2287: */
2288: supported = false;
2289: for (int i = 0; i < supportedProfiles.size(); i++) {
2290: String supportedProfile = (String) supportedProfiles
2291: .elementAt(i);
2292: if (matchVersion(supportedProfile, requestedProfile)) {
2293: supported = true;
2294: break;
2295: }
2296: }
2297:
2298: // short circuit the test if there is one mismatch
2299: if (!supported) {
2300: break;
2301: }
2302: } while (nextSeparatorIndex != -1);
2303:
2304: // matched all requested profiles against supported ones
2305: if (supported) {
2306: return;
2307: }
2308:
2309: postInstallMsgBackToProvider(OtaNotifier.INCOMPATIBLE_MSG);
2310: throw new InvalidJadException(
2311: InvalidJadException.DEVICE_INCOMPATIBLE);
2312: }
2313:
2314: /**
2315: * Registers the push connections for the application.
2316: * Send a message back to the server if a connection cannot be
2317: * registered and throw an exception.
2318: *
2319: * @exception InvalidJadException if a connection cannot be registered
2320: */
2321: private void registerPushConnections() throws InvalidJadException {
2322: byte[] curLevels = settings.getPermissions();
2323:
2324: if (state.isPreviousVersion) {
2325: PushRegistryInternal.unregisterConnections(info.id);
2326: }
2327:
2328: for (int i = 1;; i++) {
2329: String pushProp;
2330:
2331: pushProp = state.getAppProperty("MIDlet-Push-" + i);
2332: if (pushProp == null) {
2333: break;
2334: }
2335:
2336: /*
2337: * Parse the comma separated values -
2338: * " connection, midlet, role, filter"
2339: */
2340: int comma1 = pushProp.indexOf(',', 0);
2341: int comma2 = pushProp.indexOf(',', comma1 + 1);
2342:
2343: String conn = pushProp.substring(0, comma1).trim();
2344: String midlet = pushProp.substring(comma1 + 1, comma2)
2345: .trim();
2346: String filter = pushProp.substring(comma2 + 1).trim();
2347:
2348: /* Register the new push connection string. */
2349: try {
2350: PushRegistryInternal.registerConnectionInternal(state,
2351: conn, midlet, filter, false);
2352: } catch (Exception e) {
2353: /* If already registered, abort the installation. */
2354: PushRegistryInternal.unregisterConnections(info.id);
2355:
2356: if (state.isPreviousVersion) {
2357: // put back the old ones, removed above
2358: redoPreviousPushConnections();
2359: }
2360:
2361: if (e instanceof SecurityException) {
2362: postInstallMsgBackToProvider(OtaNotifier.AUTHORIZATION_FAILURE_MSG);
2363:
2364: // since our state object put the permission in message
2365: throw new InvalidJadException(
2366: InvalidJadException.AUTHORIZATION_FAILURE,
2367: e.getMessage());
2368: }
2369:
2370: postInstallMsgBackToProvider(OtaNotifier.PUSH_REG_FAILURE_MSG);
2371:
2372: if (e instanceof IllegalArgumentException) {
2373: throw new InvalidJadException(
2374: InvalidJadException.PUSH_FORMAT_FAILURE,
2375: pushProp);
2376: }
2377:
2378: if (e instanceof ConnectionNotFoundException) {
2379: throw new InvalidJadException(
2380: InvalidJadException.PUSH_PROTO_FAILURE,
2381: pushProp);
2382: }
2383:
2384: if (e instanceof IOException) {
2385: throw new InvalidJadException(
2386: InvalidJadException.PUSH_DUP_FAILURE,
2387: pushProp);
2388: }
2389:
2390: if (e instanceof ClassNotFoundException) {
2391: throw new InvalidJadException(
2392: InvalidJadException.PUSH_CLASS_FAILURE,
2393: pushProp);
2394: }
2395:
2396: // error in the implementation code
2397: throw (RuntimeException) e;
2398: }
2399: }
2400:
2401: if (state.isPreviousVersion) {
2402: // preserve the push options when updating
2403: settings.setPushOptions(state.previousSuite
2404: .getPushOptions());
2405:
2406: // use the old setting
2407: settings.setPushInterruptSetting((byte) state.previousSuite
2408: .getPushInterruptSetting());
2409:
2410: // The old suite may have not had push connections
2411: if (settings.getPushInterruptSetting() != Permissions.NEVER) {
2412: return;
2413: }
2414: }
2415:
2416: if (curLevels[Permissions.PUSH] == Permissions.NEVER) {
2417: settings.setPushInterruptSetting(Permissions.NEVER);
2418: } else if (curLevels[Permissions.PUSH] == Permissions.ALLOW) {
2419: // Start the default at session for usability when denying.
2420: settings.setPushInterruptSetting(Permissions.SESSION);
2421: } else {
2422: settings
2423: .setPushInterruptSetting(curLevels[Permissions.PUSH]);
2424: }
2425: }
2426:
2427: /**
2428: * Registers the push connections for previous version after
2429: * and aborted update.
2430: */
2431: private void redoPreviousPushConnections() {
2432: for (int i = 1;; i++) {
2433: String pushProp;
2434:
2435: pushProp = state.previousSuite.getProperty("MIDlet-Push-"
2436: + i);
2437: if (pushProp == null) {
2438: break;
2439: }
2440:
2441: /*
2442: * Parse the comma separated values -
2443: * " connection, midlet, role, filter"
2444: */
2445: int comma1 = pushProp.indexOf(',', 0);
2446: int comma2 = pushProp.indexOf(',', comma1 + 1);
2447:
2448: String conn = pushProp.substring(0, comma1).trim();
2449: String midlet = pushProp.substring(comma1 + 1, comma2)
2450: .trim();
2451: String filter = pushProp.substring(comma2 + 1).trim();
2452:
2453: /* Register the new push connection string. */
2454: try {
2455: PushRegistryInternal.registerConnectionInternal(state,
2456: conn, midlet, filter, true);
2457: } catch (IOException e) {
2458: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
2459: Logging
2460: .report(Logging.WARNING,
2461: LogChannels.LC_AMS,
2462: "registerConnectionInternal threw an IOException");
2463: }
2464: } catch (ClassNotFoundException e) {
2465: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
2466: Logging.report(Logging.WARNING, LogChannels.LC_AMS,
2467: "registerConnectionInternal threw a "
2468: + "ClassNotFoundException");
2469: }
2470: }
2471: }
2472: }
2473: }
2474:
2475: /**
2476: * Holds the state of an installation, so it can restarted after it has
2477: * been stopped.
2478: */
2479: class InstallStateImpl implements InstallState, MIDletSuite {
2480: /** Contains the data obtained during the installation process */
2481: protected InstallInfo installInfo;
2482:
2483: /** Contains the data obtained during the installation process */
2484: protected SuiteSettings suiteSettings;
2485:
2486: /** ID of the storage where the new midlet suite will be installed. */
2487: protected int storageId;
2488:
2489: /** Receives warnings and status. */
2490: protected InstallListener listener;
2491:
2492: /** When the install started, in milliseconds. */
2493: protected long startTime;
2494:
2495: /** What to do next. */
2496: protected int nextStep;
2497:
2498: /** Signals the installation to stop. */
2499: protected boolean stopInstallation;
2500:
2501: /**
2502: * Signals that installation is at a point where cancel
2503: * requests are ignored
2504: */
2505: protected boolean ignoreCancel;
2506:
2507: /** exception that stopped the installation. */
2508: protected InvalidJadException exception;
2509:
2510: /**
2511: * Option to force an overwrite of existing components without
2512: * any version comparison.
2513: */
2514: protected boolean force;
2515:
2516: /**
2517: * Option to force the RMS data of the suite to be overwritten to
2518: * be removed without comparison to the new suite.
2519: */
2520: protected boolean removeRMS;
2521:
2522: /** Raw JAD. */
2523: protected byte[] jad;
2524:
2525: /** character encoding of the JAD. */
2526: protected String jadEncoding;
2527:
2528: /** Parsed JAD. */
2529: protected JadProperties jadProps;
2530:
2531: /** Parsed manifest. */
2532: protected ManifestProperties jarProps;
2533:
2534: /** Cached File object. */
2535: protected File file;
2536:
2537: /** User name for authentication. */
2538: protected String username;
2539:
2540: /** Password for authentication. */
2541: protected String password;
2542:
2543: /** User name for proxyAuthentication. */
2544: protected String proxyUsername;
2545:
2546: /** Password for proxy authentication. */
2547: protected String proxyPassword;
2548:
2549: /** Status to signal the beginning of the data transfer. */
2550: protected int beginTransferDataStatus;
2551:
2552: /** Status for the data transfer method to give to the listener. */
2553: protected int transferStatus;
2554:
2555: /** Security Handler. */
2556: protected SecurityHandler securityHandler;
2557:
2558: /** Holds the unzipped JAR manifest to be saved. */
2559: protected byte[] manifest;
2560:
2561: /** Cache of storage object. */
2562: protected RandomAccessStream storage;
2563:
2564: /** Cache of MIDlet suite storage object. */
2565: protected MIDletSuiteStorage midletSuiteStorage;
2566:
2567: /** The root of all MIDP persistent system data. */
2568: protected String storageRoot;
2569:
2570: /** Signals that previous version exists. */
2571: protected boolean isPreviousVersion;
2572:
2573: /** Previous MIDlet suite info. */
2574: protected MIDletSuiteImpl previousSuite;
2575:
2576: /** Previous MIDlet suite install info. */
2577: protected InstallInfo previousInstallInfo;
2578:
2579: /** The ContentHandler installer state. */
2580: protected CHManager chmanager;
2581:
2582: /** Constructor. */
2583: public InstallStateImpl() {
2584: installInfo = new InstallInfo(UNUSED_SUITE_ID);
2585: suiteSettings = new SuiteSettings(UNUSED_SUITE_ID);
2586: }
2587:
2588: /**
2589: * Gets the last recoverable exception that stopped the install.
2590: * Non-recoverable exceptions are thrown and not saved in the state.
2591: *
2592: * @return last exception that stopped the install
2593: */
2594: public InvalidJadException getLastException() {
2595: return exception;
2596: }
2597:
2598: /**
2599: * Gets the unique ID that the installed suite was stored with.
2600: *
2601: * @return storage name that can be used to load the suite
2602: */
2603: public int getID() {
2604: return installInfo.id;
2605: }
2606:
2607: /**
2608: * Sets the username to be used for HTTP authentication.
2609: *
2610: * @param theUsername 8 bit username, cannot contain a ":"
2611: */
2612: public void setUsername(String theUsername) {
2613: username = theUsername;
2614: }
2615:
2616: /**
2617: * Sets the password to be used for HTTP authentication.
2618: *
2619: * @param thePassword 8 bit password
2620: */
2621: public void setPassword(String thePassword) {
2622: password = thePassword;
2623: }
2624:
2625: /**
2626: * Sets the username to be used for HTTP proxy authentication.
2627: *
2628: * @param theUsername 8 bit username, cannot contain a ":"
2629: */
2630: public void setProxyUsername(String theUsername) {
2631: proxyUsername = theUsername;
2632: }
2633:
2634: /**
2635: * Sets the password to be used for HTTP proxy authentication.
2636: *
2637: * @param thePassword 8 bit password
2638: */
2639: public void setProxyPassword(String thePassword) {
2640: proxyPassword = thePassword;
2641: }
2642:
2643: /**
2644: * Gets a property of the application to be installed.
2645: * First from the JAD, then if not found, the JAR manifest.
2646: *
2647: * @param key key of the property
2648: *
2649: * @return value of the property or null if not found
2650: */
2651: public String getAppProperty(String key) {
2652: String value;
2653:
2654: if (jadProps != null) {
2655: value = jadProps.getProperty(key);
2656: if (value != null) {
2657: return value;
2658: }
2659: }
2660:
2661: if (jarProps != null) {
2662: value = jarProps.getProperty(key);
2663: if (value != null) {
2664: return value;
2665: }
2666: }
2667:
2668: return null;
2669: }
2670:
2671: /**
2672: * Gets the URL of the JAR.
2673: *
2674: * @return URL of the JAR
2675: */
2676: public String getJarUrl() {
2677: return installInfo.jarUrl;
2678: }
2679:
2680: /**
2681: * Gets the label for the downloaded JAR.
2682: *
2683: * @return suite name
2684: */
2685: public String getSuiteName() {
2686: return installInfo.suiteName;
2687: }
2688:
2689: /**
2690: * Gets the expected size of the JAR.
2691: *
2692: * @return size of the JAR in K bytes rounded up
2693: */
2694: public int getJarSize() {
2695: return (installInfo.expectedJarSize + 1023) / 1024;
2696: }
2697:
2698: /**
2699: * Gets the authorization path of this suite. The path starts with
2700: * the most trusted CA that authorized this suite.
2701: *
2702: * @return array of CA names or null if the suite was not signed
2703: */
2704: public String[] getAuthPath() {
2705: /*
2706: * The auth path returned is no a copy because this object is
2707: * only available to callers with the AMS permission, which
2708: * have permission to build auth paths for new suites.
2709: */
2710: return installInfo.getAuthPath();
2711: }
2712:
2713: /**
2714: * Checks for permission and throw an exception if not allowed.
2715: * May block to ask the user a question.
2716: *
2717: * @param permission ID of the permission to check for,
2718: * the ID must be from
2719: * {@link com.sun.midp.security.Permissions}
2720: * @param resource string to insert into the question, can be null if
2721: * no %2 in the question
2722: *
2723: * @exception SecurityException if the permission is not
2724: * allowed by this token
2725: * @exception InterruptedException if another thread interrupts the
2726: * calling thread while this method is waiting to preempt the
2727: * display.
2728: */
2729: public void checkForPermission(int permission, String resource)
2730: throws InterruptedException {
2731: checkForPermission(permission, resource, null);
2732: }
2733:
2734: /**
2735: * Checks for permission and throw an exception if not allowed.
2736: * May block to ask the user a question.
2737: *
2738: * @param permission ID of the permission to check for,
2739: * the ID must be from
2740: * {@link com.sun.midp.security.Permissions}
2741: * @param resource string to insert into the question, can be null if
2742: * no %2 in the question
2743: * @param extraValue string to insert into the question,
2744: * can be null if no %3 in the question
2745: *
2746: * @exception SecurityException if the permission is not
2747: * allowed by this token
2748: * @exception InterruptedException if another thread interrupts the
2749: * calling thread while this method is waiting to preempt the
2750: * display.
2751: */
2752: public void checkForPermission(int permission, String resource,
2753: String extraValue) throws InterruptedException {
2754:
2755: securityHandler.checkForPermission(permission, Permissions
2756: .getTitle(permission), Permissions
2757: .getQuestion(permission), Permissions
2758: .getOneshotQuestion(permission), installInfo.suiteName,
2759: resource, extraValue, Permissions.getName(permission));
2760: }
2761:
2762: /**
2763: * Indicates if the named MIDlet is registered in the suite
2764: * with MIDlet-<n> record in the manifest or
2765: * application descriptor.
2766: * @param midletName class name of the MIDlet to be checked
2767: *
2768: * @return true if the MIDlet is registered
2769: */
2770: public boolean isRegistered(String midletName) {
2771: String midlet;
2772: MIDletInfo midletInfo;
2773:
2774: for (int i = 1;; i++) {
2775: midlet = getAppProperty("MIDlet-" + i);
2776: if (midlet == null) {
2777: return false; // We went past the last MIDlet
2778: }
2779:
2780: /* Check if the names match. */
2781: midletInfo = new MIDletInfo(midlet);
2782: if (midletInfo.classname.equals(midletName)) {
2783: return true;
2784: }
2785: }
2786: }
2787:
2788: /**
2789: * Counts the number of MIDlets from its properties.
2790: * IMPL_NOTE: refactor to avoid duplication with MIDletSuiteImpl.
2791: *
2792: * @return number of midlet in the suite
2793: */
2794: public int getNumberOfMIDlets() {
2795: int i;
2796:
2797: for (i = 1; getProperty("MIDlet-" + i) != null; i++)
2798: ;
2799:
2800: return (i - 1);
2801: }
2802:
2803: /**
2804: * Returns the suite's name to display to the user.
2805: *
2806: * @return suite's name that will be displayed to the user
2807: */
2808: public String getDisplayName() {
2809: String displayName = getAppProperty(MIDletSuite.SUITE_NAME_PROP);
2810:
2811: if (displayName == null) {
2812: displayName = String.valueOf(installInfo.id);
2813: }
2814:
2815: return displayName;
2816: }
2817:
2818: /**
2819: * Returns the information about the first midlet in the suite.
2820: *
2821: * @return MIDletInfo structure describing the first midlet
2822: * or null if it is not available
2823: */
2824: public MIDletInfo getMidletInfo() {
2825: String midlet;
2826:
2827: midlet = getAppProperty("MIDlet-1");
2828: if (midlet == null) {
2829: return null;
2830: }
2831:
2832: return new MIDletInfo(midlet);
2833: }
2834:
2835: /**
2836: * Indicates if this suite is trusted.
2837: * (not to be confused with a domain named "trusted",
2838: * this is used to determine if a trusted symbol should be displayed
2839: * to the user and not used for permissions)
2840: *
2841: * @return true if the suite is trusted false if not
2842: */
2843: public boolean isTrusted() {
2844: return installInfo.trusted;
2845: }
2846:
2847: /**
2848: * Check if the suite classes were successfully verified
2849: * during the suite installation.
2850: *
2851: * @return true if the suite classes are verified, false otherwise
2852: */
2853: public boolean isVerified() {
2854: return installInfo.verifyHash != null;
2855: }
2856:
2857: /**
2858: * Gets a property of the suite. A property is an attribute from
2859: * either the application descriptor or JAR Manifest.
2860: *
2861: * @param key the name of the property
2862: * @return A string with the value of the property.
2863: * <code>null</code> is returned if no value
2864: * is available for the key.
2865: */
2866: public String getProperty(String key) {
2867: return getAppProperty(key);
2868: }
2869:
2870: /**
2871: * Gets push setting for interrupting other MIDlets.
2872: * Reuses the Permissions.
2873: *
2874: * @return push setting for interrupting MIDlets the value
2875: * will be permission level from {@link Permissions}
2876: */
2877: public byte getPushInterruptSetting() {
2878: return suiteSettings.getPushInterruptSetting();
2879: }
2880:
2881: /**
2882: * Gets push options for this suite.
2883: *
2884: * @return push options are defined in {@link PushRegistryImpl}
2885: */
2886: public int getPushOptions() {
2887: return suiteSettings.getPushOptions();
2888: }
2889:
2890: /**
2891: * Gets list of permissions for this suite.
2892: *
2893: * @return array of permissions from {@link Permissions}
2894: */
2895: public byte[] getPermissions() {
2896: return suiteSettings.getPermissions();
2897: }
2898:
2899: /**
2900: * Replace or add a property to the suite for this run only.
2901: *
2902: * @param token token with the AMS permission set to allowed
2903: * @param key the name of the property
2904: * @param value the value of the property
2905: *
2906: * @exception SecurityException if the caller's token does not have
2907: * internal AMS permission
2908: */
2909: public void setTempProperty(SecurityToken token, String key,
2910: String value) {
2911: throw new RuntimeException("Not Implemented");
2912: }
2913:
2914: /**
2915: * Get the name of a MIDlet.
2916: *
2917: * @param classname classname of a MIDlet in the suite
2918: *
2919: * @return name of a MIDlet to show the user
2920: */
2921: public String getMIDletName(String classname) {
2922: throw new RuntimeException("Not Implemented");
2923: }
2924:
2925: /**
2926: * Checks to see the suite has the ALLOW level for specific permission.
2927: * This is used for by internal APIs that only provide access to
2928: * trusted system applications.
2929: * <p>
2930: * Only trust this method if the object has been obtained from the
2931: * MIDletStateHandler of the suite.
2932: *
2933: * @param permission permission ID from
2934: * {@link com.sun.midp.security.Permissions}
2935: *
2936: * @exception SecurityException if the suite is not
2937: * allowed to perform the specified action
2938: */
2939: public void checkIfPermissionAllowed(int permission) {
2940: throw new RuntimeException("Not Implemented");
2941: }
2942:
2943: /**
2944: * Gets the status of the specified permission.
2945: * If no API on the device defines the specific permission
2946: * requested then it must be reported as denied.
2947: * If the status of the permission is not known because it might
2948: * require a user interaction then it should be reported as unknown.
2949: *
2950: * @param permission to check if denied, allowed, or unknown
2951: * @return 0 if the permission is denied; 1 if the permission is
2952: * allowed; -1 if the status is unknown
2953: */
2954: public int checkPermission(String permission) {
2955: throw new RuntimeException("Not Implemented");
2956: }
2957:
2958: /**
2959: * Saves any the settings (security or others) that the user may have
2960: * changed. Normally called by the scheduler after
2961: * the last running MIDlet in the suite is destroyed.
2962: * However it could be call during a suspend of the VM so
2963: * that persistent settings of the suite can be preserved.
2964: */
2965: public void saveSettings() {
2966: throw new RuntimeException("Not Implemented");
2967: }
2968:
2969: /**
2970: * Asks the user want to interrupt the current MIDlet with
2971: * a new MIDlet that has received network data.
2972: *
2973: * @param connection connection to place in the permission question or
2974: * null for alarm
2975: *
2976: * @return true if the use wants interrupt the current MIDlet,
2977: * else false
2978: */
2979: public boolean permissionToInterrupt(String connection) {
2980: throw new RuntimeException("Not Implemented");
2981: }
2982:
2983: /**
2984: * Determine if the a MIDlet from this suite can be run. Note that
2985: * disable suites can still have their settings changed and their
2986: * install info displayed.
2987: *
2988: * @return true if suite is enabled, false otherwise
2989: */
2990: public boolean isEnabled() {
2991: throw new RuntimeException("Not Implemented");
2992: }
2993:
2994: /**
2995: * Close the opened MIDletSuite
2996: */
2997: public void close() {
2998: }
2999: }
|