001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.midp.content;
028:
029: import java.util.Vector;
030: import java.util.Hashtable;
031:
032: import com.sun.midp.security.Permissions;
033: import com.sun.midp.security.SecurityToken;
034:
035: import com.sun.midp.midlet.MIDletSuite;
036: import com.sun.midp.main.MIDletSuiteUtils;
037: import com.sun.midp.midlet.MIDletStateHandler;
038: import com.sun.midp.midletsuite.MIDletSuiteImpl;
039: import com.sun.midp.midletsuite.MIDletSuiteStorage;
040: import com.sun.midp.midletsuite.MIDletSuiteLockedException;
041: import com.sun.midp.midletsuite.MIDletSuiteCorruptedException;
042:
043: import com.sun.midp.events.NativeEvent;
044: import com.sun.midp.events.EventTypes;
045: import com.sun.midp.events.EventQueue;
046:
047: import com.sun.midp.io.Util;
048:
049: /**
050: * Each AppProxy instance provides access to the AMS information
051: * and functions for a running or installed application.
052: * This class must be replaced for each platform/profile combination and
053: * be integrated with the appropriate Applicaiton Management Software.
054: * <p>
055: * The AppProxy class is *only* available within
056: * this package for security purposes.
057: * An instance exists for the current application and can
058: * be retrieved for any installed application.
059: * The following methods provide functions needed by CHAPI:
060: * <ul>
061: * <li>{@link #getCurrent} - the current application and the main
062: * class within it.
063: * <li>{@link #forApp} - the AppProxy for a named Jar ID and
064: * classname. For MIDP, they are the suiteID of the storage and
065: * MIDlet within the JAR.
066: * <li>{@link #getStorageID} - the storage ID for this AppProxy.
067: * <li>{@link #getClassname} - the classname for this AppProxy.
068: * <li>{@link #getApplicationID} - the CHAPI defined unique identifier
069: * for the application
070: * <li>{@link #getApplicationName} = a user friendly name for this application.
071: * <li>{@link #getAuthority} - the authority under which the application
072: * is granted permissions. For MIDP, the subject field of the signing
073: * certificate.
074: * <li>{@link #getProperty} - access to the properties in the manifest or
075: * application descriptor.
076: * <li>{@link #getVersion} - the version number of this application.
077: * <li>{@link #getDefaultID} - the default applicationID if none is provided
078: * in the manifest or application descriptor.
079: * <li>{@link #checkRegisterPermission} - method to check if the caller
080: * is allowed to register or unregister handlers.
081: * <li>{@link #checkAPIPermission} - method to check if the caller has
082: * permission to use internal APIs. Caller must have an appropriate
083: * security token (depending on the platform)
084: * <li>{@link #isRegistered} - used to check if the application is
085: * registered as appropriate for the platform. For MIDP, there
086: * is a MIDlet-n attribute in manifest or JAD.
087: * <li>{@link #verifyApplication} - Verify that a named class is the
088: * correct type and access to be launched as an application in the
089: * profile. For MIDP, the class must extend MIDlet.
090: * <li>{@link #launch} - Request this application be launched if it is
091: * not already running. Return a boolean indicating if the current
092: * application MUST voluntarily exit before the launched app can run.
093: * <li>{@link #requestForeground} - ask the "window manager" to give the
094: * foreground to the requested application.
095: * <
096: * </ul>
097: */
098: class AppProxy {
099: /** This class has a different security domain than the MIDlet suite */
100: private static SecurityToken classSecurityToken;
101:
102: /** The current AppProxy. */
103: private static AppProxy currentApp;
104:
105: /** The log flag to enable informational messages. */
106: static final boolean LOG_INFO = false;
107:
108: /**
109: * This global state is <code>true</code> if an application
110: * has been executed and this application should be exiting
111: * to let it run. For SVM, this is set true by {@link #launch }
112: * and stays that way.
113: * For MVM, it is cleared if the VM allows the execute to occur.
114: * At MIDlet exit, {@link InvocationImpl#invokeNext} checks to see
115: * if another application Invoction is pending and invokes it.
116: * If this application has NOT invoked anything then the
117: * chosen app will be invoked.
118: */
119: private static boolean oneExecute;
120:
121: /** The known AppProxy instances. Key is classname. */
122: protected Hashtable appmap;
123:
124: /** The mutex used to avoid corruption between threads. */
125: protected static final Object mutex = new Object();
126:
127: /** The MIDlet suite for this app. */
128: protected final MIDletSuite msuite;
129:
130: /** The storageId (suiteId) for this application. */
131: protected final int storageId;
132:
133: /** The classname of the application. */
134: protected String classname;
135:
136: /** The application name. */
137: private String applicationName;
138:
139: /** The ApplicationID, (same a suiteId). */
140: private String applicationID;
141:
142: /** The application is registered. */
143: private boolean isRegistered;
144:
145: /** MIDlet property for the suite version. */
146: static final String VERSION_PROP = "MIDlet-Version";
147:
148: /** MIDlet property for the suite vendor. */
149: static final String VENDOR_PROP = "MIDlet-Vendor";
150:
151: /**
152: * Sets the security token used for priveleged operations.
153: * The token may only be set once.
154: * @param token a Security token
155: */
156: static void setSecurityToken(SecurityToken token) {
157: if (classSecurityToken != null) {
158: throw new SecurityException();
159: }
160: classSecurityToken = token;
161: }
162:
163: /**
164: * Gets the AppProxy for the currently running application.
165: * @return the current application.
166: */
167: static AppProxy getCurrent() {
168: synchronized (mutex) {
169: if (currentApp == null) {
170: MIDletStateHandler mh = MIDletStateHandler
171: .getMidletStateHandler();
172: MIDletSuite msuite = mh.getMIDletSuite();
173: try {
174: currentApp = (msuite != null) ? new AppProxy(
175: msuite, mh.getFirstRunningMidlet(), null)
176: : new AppProxy(
177: MIDletSuite.INTERNAL_SUITE_ID, "");
178: } catch (ClassNotFoundException cnfe) {
179: return null;
180: }
181: }
182: }
183: return currentApp;
184: }
185:
186: /**
187: * Construct an AppProxy with the specified MIDletSuite.
188: *
189: * @param msuite the MIDletSuite to initialize.
190: * @param classname the classname of a copackaged application.
191: * @param appmap a Hashtable to keep track of instances; may be null
192: * @exception ClassNotFoundException if the <code>classname</code>
193: * is not present
194: * @exception IllegalArgumentException if classname is not
195: * a valid application
196: */
197: protected AppProxy(MIDletSuite msuite, String classname,
198: Hashtable appmap) throws ClassNotFoundException {
199: if (appmap == null) {
200: // Allocate a Hashtable if needed
201: appmap = new Hashtable();
202: }
203:
204: this .msuite = msuite;
205: this .storageId = msuite.getID();
206: this .classname = classname;
207: this .appmap = appmap;
208: if (classname != null) {
209: verifyApplication(classname);
210: initAppInfo();
211: appmap.put(classname, this );
212: if (LOG_INFO) {
213: logInfo("AppProxy created: " + classname);
214: }
215: }
216: }
217:
218: /**
219: * Construct an AppProxy with the specified suiteId, classname.
220: * This is just a placeholder to make launch work.
221: *
222: * @param storageId the suiteId
223: * @param classname the classname
224: */
225: protected AppProxy(int storageId, String classname) {
226: this .storageId = storageId;
227: this .classname = classname;
228: MIDletSuite midletSuite = null;
229:
230: /* trying to initialize fields from suite info */
231: try {
232: midletSuite = MIDletSuiteStorage.getMIDletSuiteStorage(
233: classSecurityToken)
234: .getMIDletSuite(storageId, false);
235: } catch (MIDletSuiteLockedException msle) {
236: if (LOG_INFO) {
237: logException("AppProxy creation fails", msle);
238: }
239: } catch (MIDletSuiteCorruptedException msce) {
240: if (LOG_INFO) {
241: logException("AppProxy creation fails", msce);
242: }
243: }
244: msuite = midletSuite;
245:
246: if (msuite != null) {
247: initAppInfo();
248: }
249:
250: if (LOG_INFO) {
251: logInfo("AppProxy created: " + classname);
252: }
253: }
254:
255: /**
256: * Gets the AppProxy for an application class in the current bundle.
257: * @param classname the name of the application class
258: * @return the AppProxy for classname; <code>null</code> if not
259: * a valid application (MIDlet)
260: * @exception ClassNotFoundException if the <code>classname</code>
261: * is not present
262: * @exception IllegalArgumentException if classname is
263: * not a valid application
264: */
265: AppProxy forClass(String classname) throws ClassNotFoundException {
266: AppProxy curr = null;
267: synchronized (mutex) {
268: // Check if class already has a AppProxy
269: curr = (AppProxy) appmap.get(classname);
270: if (curr == null) {
271: // Create a new instance
272: // throws ClassNotFoundException and IllegalArgumentException
273: curr = new AppProxy(msuite, classname, appmap);
274: }
275: }
276: return curr;
277: }
278:
279: /**
280: * Gets the AppProxy for an storageID and classname.
281: * @param storageId the storageId (suiteId)
282: * @param classname the name of the application class
283: * @return the AppProxy for suiteId, classname;
284: * <code>null</code> if not a valid application (MIDlet)
285: * @exception ClassNotFoundException if the <code>classname</code>
286: * is not present
287: * @exception IllegalArgumentException if classname is not
288: * a valid application
289: */
290: AppProxy forApp(int storageId, String classname)
291: throws ClassNotFoundException {
292: // Check in the current suite
293: if (storageId == this .storageId) {
294: return forClass(classname);
295: }
296:
297: // Create a new instance
298: AppProxy curr = new AppProxy(storageId, classname);
299:
300: return curr;
301: }
302:
303: /**
304: * Gets the storage ID of this application.
305: * The ID uniquely identifies the package/application bundle.
306: * @return the application ID.
307: */
308: int getStorageId() {
309: return storageId;
310: }
311:
312: /**
313: * Gets the classname of this application.
314: * @return the classname
315: */
316: String getClassname() {
317: return classname;
318: }
319:
320: /**
321: * Gets the user friendly application name.
322: * @return the user friendly application name
323: */
324: String getApplicationName() {
325: return applicationName;
326: }
327:
328: /**
329: * Gets the CHAPI application ID for this application.
330: * @return the CHAPI application ID.
331: */
332: String getApplicationID() {
333: return applicationID;
334: }
335:
336: /**
337: * Gets the version string for the application.
338: * @return the version
339: */
340: String getVersion() {
341: return msuite == null ? null : msuite.getProperty(VERSION_PROP);
342: }
343:
344: /**
345: * Gets the Trusted authority that authenticated this application.
346: * <p>
347: * For MIDP, this is the CA of the signer.
348: * If exception is thrown during getting authorization
349: * this methods ignores it and returns null.
350: * @return the authority.
351: */
352: String getAuthority() {
353: String auth = null;
354: try {
355: auth = ((MIDletSuiteImpl) msuite).getInstallInfo().getCA();
356: } catch (RuntimeException e) {
357: }
358: return auth;
359: }
360:
361: /**
362: * Gets true if the application is a registered application.
363: * <p>
364: * for MIDP, this means there was a MIDlet-n attribute.
365: * @return true if this application is registered
366: */
367: boolean isRegistered() {
368: return isRegistered;
369: }
370:
371: /**
372: * Gets a property from the manifest or application descriptor.
373: * @param key the name of the property to retrieve
374: * @return the value of the property or <code>null</code>
375: */
376: String getProperty(String key) {
377: return msuite.getProperty(key);
378: }
379:
380: /**
381: * Check the permission to register or unregister.
382: * @param reason the reason for the permission check
383: * @exception SecurityException if not allowed
384: */
385: final void checkRegisterPermission(String reason) {
386: try {
387: msuite.checkForPermission(Permissions.CHAPI_REGISTER,
388: getApplicationName(), reason);
389: } catch (InterruptedException ie) {
390: throw new SecurityException("interrupted");
391: }
392: }
393:
394: /**
395: * Check if the internal API use is allowed.
396: * @param securityToken a generic security token
397: * @exception SecurityException thrown if internal API use not allowed
398: */
399: final static void checkAPIPermission(Object securityToken) {
400: if (securityToken != null) {
401: ((SecurityToken) securityToken)
402: .checkIfPermissionAllowed(Permissions.MIDP);
403: } else {
404: MIDletSuite msuite = MIDletStateHandler
405: .getMidletStateHandler().getMIDletSuite();
406: if (msuite != null) {
407: msuite.checkIfPermissionAllowed(Permissions.AMS);
408: }
409: }
410: }
411:
412: /**
413: * Request the transition of the foreground to this application
414: * from the invoking application.
415: * @param invokingSuiteId the invoking suiteId
416: * @param invokingClassname the invoking classname
417: * @param targetSuiteId the target suiteId
418: * @param targetClassname the target classname
419: */
420: static void requestForeground(int invokingSuiteId,
421: String invokingClassname, int targetSuiteId,
422: String targetClassname) {
423: NativeEvent event = new NativeEvent(
424: EventTypes.FOREGROUND_TRANSFER_EVENT);
425: event.intParam1 = invokingSuiteId;
426: event.stringParam1 = invokingClassname;
427: event.intParam2 = targetSuiteId;
428: event.stringParam2 = targetClassname;
429:
430: int amsIsolateId = MIDletSuiteUtils.getAmsIsolateId();
431: EventQueue eventQueue = EventQueue
432: .getEventQueue(classSecurityToken);
433: eventQueue.sendNativeEventToIsolate(event, amsIsolateId);
434: }
435:
436: /**
437: * The stronger variant for request the transition of
438: * the foreground to this application.
439: * @param targetSuiteId the target suiteId
440: * @param targetClassname the target classname
441: */
442: static void requestForeground(int targetSuiteId,
443: String targetClassname) {
444: NativeEvent event = new NativeEvent(
445: EventTypes.SET_FOREGROUND_BY_NAME_REQUEST);
446: event.intParam1 = targetSuiteId;
447: event.stringParam1 = targetClassname;
448:
449: int amsIsolateId = MIDletSuiteUtils.getAmsIsolateId();
450: EventQueue eventQueue = EventQueue
451: .getEventQueue(classSecurityToken);
452: eventQueue.sendNativeEventToIsolate(event, amsIsolateId);
453: }
454:
455: /**
456: * Launch this application.
457: * Don't launch another application unless
458: * the execute allows this application to continue after
459: * the launch.
460: * <p>
461: * In SVM, (sequential applications) only the first
462: * execute matters; later ones should not override the
463: * first. All pending Invocations are queued in InvocationStore
464: * so they will not be lost. When MIDlets exit, another
465: * application will be selected from those pending.
466: *
467: * @param displayName name to show to the user of what to launch
468: * @return <code>true</code> if the application must exist
469: * before the target application can proceed.
470: */
471: boolean launch(String displayName) {
472: /*
473: * If an execute has been queued already; don't queue another
474: */
475: if (oneExecute) {
476: // launched something previously and app should exit.
477: if (LOG_INFO) {
478: logInfo("Launch skipped: " + classname
479: + ", oneExecute: " + oneExecute);
480: }
481: return true;
482: } else {
483: /* Invoke the target application */
484: oneExecute = MIDletSuiteUtils.execute(classSecurityToken,
485: storageId, classname, displayName);
486: if (LOG_INFO) {
487: logInfo("Launch: " + classname + ", oneExecute: "
488: + oneExecute);
489: }
490: return oneExecute;
491: }
492: }
493:
494: /**
495: * Verify that the classname is a valid application.
496: * It must extend MIDlet.
497: * @param classname the application class
498: *
499: * @exception ClassNotFoundException is thrown if the class cannot be found
500: * @exception IllegalArgumentException if the classname is null or empty
501: * or does not implement the lifecycle of a MIDlet.
502: */
503: protected void verifyApplication(String classname)
504: throws ClassNotFoundException {
505: /* check the classname for null and get the class */
506: Class appClass = Class.forName(classname);
507: Class midletClass = Class
508: .forName("javax.microedition.midlet.MIDlet");
509: if ((!midletClass.isAssignableFrom(appClass))
510: || appClass == midletClass) {
511: throw new IllegalArgumentException("not a MIDlet");
512: }
513: }
514:
515: /**
516: * Initialize application name and application ID
517: * from the attributes.
518: */
519: protected void initAppInfo() {
520: // Check if it is an internal MIDlet
521: if (storageId == MIDletSuite.INTERNAL_SUITE_ID) {
522: applicationName = classname.substring(classname
523: .lastIndexOf('.') + 1);
524: applicationID = "system";
525: isRegistered = true;
526: return;
527: }
528:
529: // Check if a registered MIDlet
530: String[] minfo = getMIDletInfo(msuite, classname);
531:
532: // if a MIDlet, set the application name and application ID
533: if (minfo != null) {
534: applicationName = minfo[0];
535: applicationID = minfo[2];
536: isRegistered = true;
537: }
538:
539: // Fill in defaults for appName and applicationID
540: if (applicationName == null || applicationName.length() == 0) {
541: applicationName = msuite
542: .getProperty(MIDletSuiteImpl.SUITE_NAME_PROP);
543: }
544:
545: if (applicationID == null || applicationID.length() == 0) {
546: applicationID = getDefaultID();
547: }
548: }
549:
550: /**
551: * Gets the content handler ID for the current application.
552: * The ID uniquely identifies the application which contains the
553: * content handler.
554: * The application ID is assigned when the application is installed.
555: * <p>
556: * All installed applications have vendor and name;
557: *
558: * @return the ID; MUST NOT be <code>null</code>
559: */
560: String getDefaultID() {
561: StringBuffer sb = new StringBuffer(80);
562: String s = msuite.getProperty(VENDOR_PROP);
563: sb.append((s != null) ? s : "internal");
564: sb.append('-');
565: s = msuite.getProperty(MIDletSuiteImpl.SUITE_NAME_PROP);
566: sb.append((s != null) ? s : "system");
567: sb.append('-');
568: sb.append(classname);
569: return sb.toString().replace(' ', '_');
570: }
571:
572: /**
573: * Get the MIDletInfo for the named MIDlet.
574: * @param suite the MIDlet suite to look in for the midlet
575: * @param classname the class name to look for
576: * @return an array of Strings, name, icon, ID
577: * null if there is no matching MIDlet-n.
578: */
579: private static String[] getMIDletInfo(MIDletSuite suite,
580: String classname) {
581: for (int i = 1;; i++) {
582: String midletn = "MIDlet-".concat(Integer.toString(i));
583: String attr = suite.getProperty(midletn);
584: if (attr == null) {
585: break; // break out of loop, not found
586: }
587:
588: Vector args = Util.getCommaSeparatedValues(attr);
589: if (args.size() < 3) {
590: // Not enough args to be legit
591: continue;
592: }
593:
594: if (!classname.equals(args.elementAt(2))) {
595: continue;
596: }
597: String[] values = new String[args.size()];
598: args.copyInto(values);
599:
600: String ID = suite.getProperty(midletn.concat("-ID"));
601: values[2] = ID;
602:
603: return values;
604: }
605: return null;
606: }
607:
608: /**
609: * Log an information message to the system logger for this AppProxy.
610: * @param msg a message to write to the log.
611: */
612: void logInfo(String msg) {
613: if (LOG_INFO) {
614: System.out.println(">> " + threadID() + ": " + msg);
615: }
616: }
617:
618: /**
619: * Log an information message to the system logger for this AppProxy.
620: * @param msg a message to write to the log.
621: * @param t Throwable to be logged
622: */
623: void logException(String msg, Throwable t) {
624: if (LOG_INFO) {
625: System.out.println("** " + threadID() + ": " + msg);
626: t.printStackTrace();
627: }
628: }
629:
630: /**
631: * Create a printable representation of this AppProxy.
632: * @return a printable string
633: */
634: public String toString() {
635: if (LOG_INFO) {
636: return "class: " + classname + ", suite: " + storageId
637: + ", registered: " + isRegistered + ", name: "
638: + applicationName + ", ID: " + applicationID;
639: } else {
640: return super .toString();
641: }
642: }
643:
644: /**
645: * Map a thread to an printable string.
646: * @return a short string for the thread
647: */
648: private String threadID() {
649: if (LOG_INFO) {
650: Thread thread = Thread.currentThread();
651: int i = thread.hashCode() & 0xff;
652: return "T" + i;
653: } else {
654: return "";
655: }
656: }
657: }
|