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.installer;
028:
029: import java.io.*;
030:
031: import java.util.*;
032:
033: import javax.microedition.io.*;
034:
035: import javax.microedition.lcdui.*;
036:
037: import javax.microedition.midlet.*;
038:
039: import javax.microedition.rms.*;
040:
041: import com.sun.midp.i18n.Resource;
042: import com.sun.midp.i18n.ResourceConstants;
043:
044: import com.sun.midp.midlet.*;
045:
046: import com.sun.midp.log.Logging;
047: import com.sun.midp.log.LogChannels;
048: import javax.microedition.lcdui.List;
049:
050: /**
051: * The Graphical MIDlet suite Discovery Application.
052: * <p>
053: * Let the user install a suite from a list of suites
054: * obtained using an HTML URL given by the user. This list is derived by
055: * extracting the links with hrefs that are in quotes and end with ".jad" from
056: * the HTML page. An href in an extracted link is assumed to be an absolute
057: * URL for a MIDP application descriptor. The selected URL is then passed to
058: * graphical Installer.
059: */
060: public class DiscoveryApp extends MIDlet implements CommandListener {
061:
062: /** Display for this MIDlet. */
063: private Display display;
064: /** Contains the default URL for the install list. */
065: private String defaultInstallListUrl = "http://";
066: /** Contains the URL the user typed in. */
067: private TextBox urlTextBox;
068: /** Displays the progress of the install. */
069: private Form progressForm;
070: /** Gauge for progress form index. */
071: private int progressGaugeIndex;
072: /** URL for progress form index. */
073: private int progressUrlIndex;
074: /** Keeps track of when the display last changed, in milliseconds. */
075: private long lastDisplayChange;
076: /** Displays a list of suites to install to the user. */
077: private List installListBox;
078: /** Contains a list of suites to install. */
079: private Vector installList;
080:
081: /** Command object for URL screen to go and discover available suites. */
082: private Command discoverCmd = new Command(Resource
083: .getString(ResourceConstants.GOTO), Command.SCREEN, 1);
084: /** Command object for "Install" command in the suite list form . */
085: private Command installCmd = new Command(Resource
086: .getString(ResourceConstants.INSTALL), Command.ITEM, 1);
087: /** Command object for "Back" command in the suite list form. */
088: private Command backCmd = new Command(Resource
089: .getString(ResourceConstants.BACK), Command.BACK, 1);
090: /** Command object for URL screen to save the URL for suites. */
091: private Command saveCmd = new Command(Resource
092: .getString(ResourceConstants.SAVE), Command.SCREEN, 2);
093:
094: /** Command object for "Back" command in the URL form. */
095: private Command endCmd = new Command(Resource
096: .getString(ResourceConstants.BACK), Command.BACK, 1);
097:
098: /**
099: * Create and initialize a new discovery application MIDlet.
100: * The saved URL is retrieved and the list of MIDlets are retrieved.
101: */
102: public DiscoveryApp() {
103: String storageName;
104:
105: display = Display.getDisplay(this );
106:
107: GraphicalInstaller.initSettings();
108: restoreSettings();
109:
110: // get the URL of a list of suites to install
111: getUrl();
112: }
113:
114: /**
115: * Start.
116: */
117: public void startApp() {
118: }
119:
120: /**
121: * Pause; there are no resources that need to be released.
122: */
123: public void pauseApp() {
124: }
125:
126: /**
127: * Destroy cleans up.
128: *
129: * @param unconditional is ignored; this object always
130: * destroys itself when requested.
131: */
132: public void destroyApp(boolean unconditional) {
133: }
134:
135: /**
136: * Respond to a command issued on any Screen.
137: *
138: * @param c command activated by the user
139: * @param s the Displayable the command was on.
140: */
141: public void commandAction(Command c, Displayable s) {
142: if (c == discoverCmd) {
143: // user wants to discover the suites that can be installed
144: discoverSuitesToInstall(urlTextBox.getString());
145: } else if (s == installListBox
146: && (c == List.SELECT_COMMAND || c == installCmd)) {
147: installSuite(installListBox.getSelectedIndex());
148: } else if (c == backCmd) {
149: display.setCurrent(urlTextBox);
150: } else if (c == saveCmd) {
151: saveURLSetting();
152: } else if (c == endCmd || c == Alert.DISMISS_COMMAND) {
153: // goto back to the manager midlet
154: notifyDestroyed();
155: }
156: }
157:
158: /**
159: * Get the settings the Manager saved for the user.
160: */
161: private void restoreSettings() {
162: ByteArrayInputStream bas;
163: DataInputStream dis;
164: byte[] data;
165: RecordStore settings = null;
166:
167: try {
168: settings = RecordStore.openRecordStore(
169: GraphicalInstaller.SETTINGS_STORE, false);
170:
171: data = settings.getRecord(1);
172: if (data != null) {
173: bas = new ByteArrayInputStream(data);
174: dis = new DataInputStream(bas);
175: defaultInstallListUrl = dis.readUTF();
176: }
177:
178: } catch (RecordStoreException e) {
179: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
180: Logging.report(Logging.WARNING, LogChannels.LC_AMS,
181: "restoreSettings threw a RecordStoreException");
182: }
183: } catch (IOException e) {
184: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
185: Logging.report(Logging.WARNING, LogChannels.LC_AMS,
186: "restoreSettings threw an IOException");
187: }
188: } finally {
189: if (settings != null) {
190: try {
191: settings.closeRecordStore();
192: } catch (RecordStoreException e) {
193: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
194: Logging
195: .report(Logging.WARNING,
196: LogChannels.LC_AMS,
197: "closeRecordStore threw a RecordStoreException");
198: }
199: }
200: }
201: }
202: }
203:
204: /**
205: * Save the URL setting the user entered in to the urlTextBox.
206: */
207: private void saveURLSetting() {
208: String temp;
209: Exception ex;
210:
211: temp = urlTextBox.getString();
212:
213: ex = GraphicalInstaller.saveSettings(temp,
214: MIDletSuite.INTERNAL_SUITE_ID);
215: if (ex != null) {
216: displayException(Resource
217: .getString(ResourceConstants.EXCEPTION), ex
218: .toString());
219: return;
220: }
221:
222: defaultInstallListUrl = temp;
223:
224: displaySuccessMessage(Resource
225: .getString(ResourceConstants.AMS_MGR_SAVED));
226: }
227:
228: /**
229: * Alert the user that an action was successful.
230: *
231: * @param successMessage message to display to user
232: */
233: private void displaySuccessMessage(String successMessage) {
234: Image icon;
235: Alert successAlert;
236:
237: icon = GraphicalInstaller
238: .getImageFromInternalStorage("_dukeok8");
239:
240: successAlert = new Alert(null, successMessage, icon, null);
241:
242: successAlert.setTimeout(GraphicalInstaller.ALERT_TIMEOUT);
243:
244: // We need to prevent "flashing" on fast development platforms.
245: while (System.currentTimeMillis() - lastDisplayChange < GraphicalInstaller.ALERT_TIMEOUT)
246: ;
247:
248: lastDisplayChange = System.currentTimeMillis();
249: display.setCurrent(successAlert);
250: }
251:
252: /**
253: * Let the user select a suite to install. The suites that are listed
254: * are the links on a web page that end with .jad.
255: *
256: * @param url where to get the list of suites to install.
257: */
258: private void discoverSuitesToInstall(String url) {
259: new Thread(new BackgroundInstallListGetter(this , url)).start();
260: }
261:
262: /**
263: * Display the connecting form to the user, let call set actions.
264: *
265: * @param action action to put in the form's title
266: * @param name name to in the form's title
267: * @param url URL of a JAD
268: * @param size 0 if unknown, else size of object to download in K bytes
269: * @param gaugeLabel label for progress gauge
270: *
271: * @return displayed form
272: */
273: private Form displayProgressForm(String action, String name,
274: String url, int size, String gaugeLabel) {
275: Gauge progressGauge;
276: StringItem urlItem;
277:
278: progressForm = new Form(null);
279:
280: progressForm.setTitle(action + " " + name);
281:
282: if (size <= 0) {
283: progressGauge = new Gauge(gaugeLabel, false,
284: Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING);
285: } else {
286: progressGauge = new Gauge(gaugeLabel, false, size, 0);
287: }
288:
289: progressGaugeIndex = progressForm.append(progressGauge);
290:
291: if (url == null) {
292: urlItem = new StringItem("", "");
293: } else {
294: urlItem = new StringItem(Resource
295: .getString(ResourceConstants.AMS_WEBSITE)
296: + ": ", url);
297: }
298:
299: progressUrlIndex = progressForm.append(urlItem);
300:
301: display.setCurrent(progressForm);
302: lastDisplayChange = System.currentTimeMillis();
303:
304: return progressForm;
305: }
306:
307: /**
308: * Install a suite.
309: *
310: * @param selectedSuite index into the installList
311: */
312: private void installSuite(int selectedSuite) {
313: MIDletStateHandler midletStateHandler = MIDletStateHandler
314: .getMidletStateHandler();
315: MIDletSuite midletSuite = midletStateHandler.getMIDletSuite();
316: SuiteDownloadInfo suite;
317: String displayName;
318:
319: suite = (SuiteDownloadInfo) installList
320: .elementAt(selectedSuite);
321:
322: midletSuite.setTempProperty(null, "arg-0", "I");
323: midletSuite.setTempProperty(null, "arg-1", suite.url);
324: midletSuite.setTempProperty(null, "arg-2", suite.label);
325:
326: displayName = Resource
327: .getString(ResourceConstants.INSTALL_APPLICATION);
328: try {
329: midletStateHandler.startMIDlet(
330: "com.sun.midp.installer.GraphicalInstaller",
331: displayName);
332: /*
333: * Give the create MIDlet notification 1 second to get to
334: * AMS.
335: */
336: Thread.sleep(1000);
337: notifyDestroyed();
338: } catch (Exception ex) {
339: StringBuffer sb = new StringBuffer();
340:
341: sb.append(displayName);
342: sb.append("\n");
343: sb.append(Resource.getString(ResourceConstants.ERROR));
344: sb.append(": ");
345: sb.append(ex.toString());
346:
347: Alert a = new Alert(Resource
348: .getString(ResourceConstants.AMS_CANNOT_START), sb
349: .toString(), null, AlertType.ERROR);
350: a.setTimeout(Alert.FOREVER);
351: display.setCurrent(a, urlTextBox);
352: }
353: }
354:
355: /**
356: * Update URL and gauge of the progress form.
357: *
358: * @param url new URL, null to remove, "" to not change
359: * @param size 0 if unknown, else size of object to download in K bytes
360: * @param gaugeLabel label for progress gauge
361: */
362: private void updateProgressForm(String url, int size,
363: String gaugeLabel) {
364: Gauge oldProgressGauge;
365: Gauge progressGauge;
366: StringItem urlItem;
367:
368: // We need to prevent "flashing" on fast development platforms.
369: while (System.currentTimeMillis() - lastDisplayChange < GraphicalInstaller.ALERT_TIMEOUT)
370: ;
371:
372: if (size <= 0) {
373: progressGauge = new Gauge(gaugeLabel, false,
374: Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING);
375: } else {
376: progressGauge = new Gauge(gaugeLabel, false, size, 0);
377: }
378:
379: oldProgressGauge = (Gauge) progressForm.get(progressGaugeIndex);
380: progressForm.set(progressGaugeIndex, progressGauge);
381:
382: // this ends the background thread of gauge.
383: oldProgressGauge.setValue(Gauge.CONTINUOUS_IDLE);
384:
385: if (url == null) {
386: urlItem = new StringItem("", "");
387: progressForm.set(progressUrlIndex, urlItem);
388: } else if (url.length() != 0) {
389: urlItem = new StringItem(Resource
390: .getString(ResourceConstants.AMS_WEBSITE)
391: + ": ", url);
392: progressForm.set(progressUrlIndex, urlItem);
393: }
394:
395: lastDisplayChange = System.currentTimeMillis();
396: }
397:
398: /**
399: * Ask the user for the URL.
400: */
401: private void getUrl() {
402: try {
403: if (urlTextBox == null) {
404: urlTextBox = new TextBox(
405: Resource
406: .getString(ResourceConstants.AMS_DISC_APP_WEBSITE_INSTALL),
407: defaultInstallListUrl, 1024, TextField.ANY);
408: urlTextBox.addCommand(endCmd);
409: urlTextBox.addCommand(saveCmd);
410: urlTextBox.addCommand(discoverCmd);
411: urlTextBox.setCommandListener(this );
412: }
413:
414: display.setCurrent(urlTextBox);
415: } catch (Exception ex) {
416: displayException(Resource
417: .getString(ResourceConstants.EXCEPTION), ex
418: .toString());
419: }
420: }
421:
422: /**
423: * Display an exception to the user, with a done command.
424: *
425: * @param title exception form's title
426: * @param message exception message
427: */
428: private void displayException(String title, String message) {
429: Alert a = new Alert(title, message, null, AlertType.ERROR);
430:
431: a.setTimeout(Alert.FOREVER);
432: a.setCommandListener(this );
433:
434: display.setCurrent(a);
435: }
436:
437: /** A class to get the install list in a background thread. */
438: private class BackgroundInstallListGetter implements Runnable {
439: /** Parent application. */
440: private DiscoveryApp parent;
441: /** URL of the list. */
442: private String url;
443:
444: /**
445: * Construct a BackgroundInstallListGetter.
446: *
447: * @param theParent parent of this object
448: * @param theUrl where to get the list of suites to install.
449: */
450: private BackgroundInstallListGetter(DiscoveryApp theParent,
451: String theUrl) {
452: parent = theParent;
453: url = theUrl;
454: }
455:
456: /**
457: * Get the list of suites for the user to install.
458: * The suites that are listed
459: * are the links on a web page that end with .jad.
460: */
461: public void run() {
462: StreamConnection conn = null;
463: InputStreamReader in = null;
464: String errorMessage;
465: long startTime;
466:
467: startTime = System.currentTimeMillis();
468:
469: try {
470: parent
471: .displayProgressForm(
472: Resource
473: .getString(ResourceConstants.AMS_DISC_APP_GET_INSTALL_LIST),
474: "",
475: url,
476: 0,
477: Resource
478: .getString(ResourceConstants.AMS_GRA_INTLR_CONN_GAUGE_LABEL));
479: conn = (StreamConnection) Connector.open(url,
480: Connector.READ);
481: in = new InputStreamReader(conn.openInputStream());
482: try {
483: parent
484: .updateProgressForm(
485: "",
486: 0,
487: Resource
488: .getString(ResourceConstants.AMS_DISC_APP_GAUGE_LABEL_DOWNLOAD));
489:
490: parent.installList = SuiteDownloadInfo
491: .getDownloadInfoFromPage(in);
492:
493: if (parent.installList.size() > 0) {
494: parent.installListBox = new List(
495: Resource
496: .getString(ResourceConstants.AMS_DISC_APP_SELECT_INSTALL),
497: Choice.IMPLICIT);
498:
499: // Add each suite
500: for (int i = 0; i < parent.installList.size(); i++) {
501: SuiteDownloadInfo suite = (SuiteDownloadInfo) installList
502: .elementAt(i);
503: parent.installListBox.append(suite.label,
504: (Image) null);
505: }
506:
507: parent.installListBox
508: .addCommand(parent.backCmd);
509: parent.installListBox
510: .addCommand(parent.installCmd);
511: parent.installListBox
512: .setCommandListener(parent);
513:
514: /*
515: * We need to prevent "flashing" on fast development
516: * platforms.
517: */
518: while (System.currentTimeMillis()
519: - parent.lastDisplayChange < GraphicalInstaller.ALERT_TIMEOUT)
520: ;
521:
522: parent.display
523: .setCurrent(parent.installListBox);
524: return;
525: }
526:
527: errorMessage = Resource
528: .getString(ResourceConstants.AMS_DISC_APP_CHECK_URL_MSG);
529: } catch (IllegalArgumentException ex) {
530: errorMessage = Resource
531: .getString(ResourceConstants.AMS_DISC_APP_URL_FORMAT_MSG);
532: } catch (Exception ex) {
533: errorMessage = ex.getMessage();
534: }
535: } catch (Exception ex) {
536: errorMessage = Resource
537: .getString(ResourceConstants.AMS_DISC_APP_CONN_FAILED_MSG);
538: } finally {
539: if (parent.progressForm != null) {
540: // end the background thread of progress gauge.
541: Gauge progressGauge = (Gauge) parent.progressForm
542: .get(parent.progressGaugeIndex);
543: progressGauge.setValue(Gauge.CONTINUOUS_IDLE);
544: }
545:
546: try {
547: conn.close();
548: in.close();
549: } catch (Exception e) {
550: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
551: Logging.report(Logging.WARNING,
552: LogChannels.LC_AMS,
553: "close threw an Exception");
554: }
555: }
556: }
557:
558: Alert a = new Alert(Resource
559: .getString(ResourceConstants.ERROR), errorMessage,
560: null, AlertType.ERROR);
561: a.setTimeout(Alert.FOREVER);
562: parent.display.setCurrent(a, parent.urlTextBox);
563: }
564: }
565: }
|