package rhomobile; import j2me.util.LinkedList; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import javax.microedition.io.HttpConnection; import net.rim.device.api.browser.field.RenderingOptions; import net.rim.device.api.io.http.HttpHeaders; import net.rim.device.api.system.Application; import net.rim.device.api.system.ApplicationManager; import net.rim.device.api.system.Bitmap; import net.rim.device.api.system.Characters; import net.rim.device.api.system.Display; import net.rim.device.api.system.EncodedImage; import net.rim.device.api.system.KeyListener; import net.rim.device.api.system.SystemListener; //import javax.microedition.io.file.FileSystemListener; import net.rim.device.api.system.TrackwheelListener; import net.rim.device.api.ui.*; import net.rim.device.api.ui.component.BitmapField; import net.rim.device.api.ui.component.Dialog; import net.rim.device.api.ui.component.Menu; import net.rim.device.api.ui.container.PopupScreen; import net.rim.device.api.ui.container.VerticalFieldManager; //import net.rim.device.api.ui.container.HorizontalFieldManager; import net.rim.device.api.ui.component.ButtonField; import net.rim.device.api.ui.component.LabelField; import net.rim.device.api.ui.Manager; import net.rim.device.api.math.Fixed32; //import net.rim.device.api.system.EventInjector.KeyCodeEvent; import com.rho.*; //import com.rho.db.DBAdapter; import com.rho.rubyext.GeoLocation; import com.rho.net.NetResponse; import com.rho.net.RhoConnection; import com.rho.net.URI; import com.rho.sync.ClientRegister; import com.rho.sync.SyncThread; import com.rho.sync.ISyncStatusListener; import com.rho.file.Jsr75File; import com.rho.RhodesApp; import com.xruby.runtime.lang.RubyProgram; /** * */ final public class RhodesApplication extends UiApplication implements SystemListener, ISyncStatusListener//, FileSystemListener { // Menu Labels public static final String LABEL_HOME = "Home"; public static final String LABEL_REFRESH = "Refresh"; public static final String LABEL_BACK = "Back"; public static final String LABEL_SYNC = "Sync"; public static final String LABEL_OPTIONS = "Options"; public static final String LABEL_LOG = "Log"; public static final String LABEL_SEPARATOR = "separator"; public static final String LABEL_CLOSE = "Close"; public static final String LABEL_EXIT = "Exit"; public static final String LABEL_NONE = "none"; private static final RhoLogger LOG = RhoLogger.RHO_STRIP_LOG ? new RhoEmptyLogger() : new RhoLogger("RhodesApplication"); class CKeyListener implements KeyListener{ public boolean keyChar(char key, int status, int time) { if( key == Characters.ENTER ) { openLink(); return true; } return false; } public boolean keyDown(int keycode, int time) { int nKey = Keypad.key(keycode); if ( nKey == Keypad.KEY_ESCAPE ) { /*if ( m_bSkipKeyPress ) m_bSkipKeyPress = false; else*/ back(); return true; } return false; } public boolean keyRepeat(int keycode, int time) {return false;} public boolean keyStatus(int keycode, int time) {return false;} public boolean keyUp(int keycode, int time) {return false;} }; class CTrackwheelListener implements TrackwheelListener{ public boolean trackwheelClick(int status, int time) { openLink(); return true; } public boolean trackwheelRoll(int amount, int status, int time) { return false; } public boolean trackwheelUnclick(int status, int time) {return false;} } public void navigateUrl(String url){ PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread( RHODESAPP().canonicalizeRhoUrl(url), null, null, null); thread.start(); } public void addMenuItem(String label, String value){ LOG.TRACE("Adding menu item: label: " + label + ", value: " + value); _mainScreen.addCustomMenuItem(label, value); } private String m_strAppBackUrl =""; public void resetMenuItems() { _mainScreen.setMenuItems(new Vector()); m_strAppBackUrl = ""; } public void postUrl(String url, String body, HttpHeaders headers) { postUrl(url, body, headers, null); } public void postUrl(String url, String body, HttpHeaders headers, Runnable callback){ PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread( RHODESAPP().canonicalizeRhoUrl(url), headers, body.getBytes(), null, callback); thread.setInternalRequest(true); thread.start(); } public static class NetCallback { public NetResponse m_response; public void waitForResponse() { synchronized(this) { try{ this.wait(); }catch(InterruptedException exc){} } } public void setResponse(NetResponse resp) { synchronized(this) { m_response = resp; this.notifyAll(); } } } public void postUrlWithCallback(String url, String body, HttpHeaders headers, NetCallback netCallback){ PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread( RHODESAPP().canonicalizeRhoUrl(url), headers, body.getBytes(), null); thread.setNetCallback(netCallback); thread.start(); } void saveCurrentLocation(String url) { if (RhoConf.getInstance().getBool("KeepTrackOfLastVisitedPage")) { RhoConf.getInstance().setString("LastVisitedPage",url); RhoConf.getInstance().saveToFile(); LOG.TRACE("Saved LastVisitedPage: " + url); } } boolean restoreLocation() { LOG.TRACE("Restore Location to LastVisitedPage"); if (RhoConf.getInstance().getBool("KeepTrackOfLastVisitedPage")) { String url = RhoConf.getInstance().getString("LastVisitedPage"); if (url.length()>0) { LOG.TRACE("Navigating to LastVisitedPage: " + url); if ( _history.size() == 0 ) _history.addElement(url); navigateUrl(url); return true; } } return false; } void back(){ String url = m_strAppBackUrl; if ( url.length() == 0) { if ( _history.size() <= 1 ) { if ( RhoConf.getInstance().getBool("bb_disable_closebyback")) return; _mainScreen.close(); return; } int nPos = _history.size()-2; url = (String)_history.elementAt(nPos); _history.removeElementAt(nPos+1); // this.m_oBrowserAdapter.goBack(); }else if ( url.equalsIgnoreCase("close")) { _mainScreen.close(); return; }else addToHistory(url,null); saveCurrentLocation(url); navigateUrl(url); } String removeSemicolon(String str) { if ( str == null ) return null; int nCol = str.indexOf(';'); if ( nCol >= 0 ) return str.substring(0,nCol); return str; } public void addToHistory(String strUrl, String refferer ) { strUrl = removeSemicolon(strUrl); refferer = removeSemicolon(refferer); strUrl = RHODESAPP().canonicalizeRhoUrl(strUrl); // if ( !strUrl.startsWith(_httpRoot) && !isExternalUrl(strUrl) ) // strUrl = _httpRoot + (strUrl.startsWith("/") ? strUrl.substring(1) : strUrl); int nPos = -1; for( int i = 0; i < _history.size(); i++ ){ if ( strUrl.equalsIgnoreCase((String)_history.elementAt(i)) ){ nPos = i; break; } /*String strUrl1 = strUrl + "/index"; if ( strUrl1.equalsIgnoreCase((String)_history.elementAt(i)) ){ nPos = i; break; }*/ if ( refferer != null && refferer.equalsIgnoreCase((String)_history.elementAt(i)) ){ nPos = i; break; } } if ( nPos == -1 ){ boolean bReplace = RhoConnection.findIndex(strUrl) != -1; if ( bReplace ) _history.setElementAt(strUrl, _history.size()-1 ); else _history.addElement(strUrl); } else{ _history.setSize(nPos+1); _history.setElementAt(strUrl, _history.size()-1 ); } saveCurrentLocation(strUrl); } private boolean m_bOpenLink = false; private String m_strGetLink, m_strEmailMenu, m_strCallMenu; boolean openLink(){ LOG.INFO("openLink"); try{ m_bOpenLink = true; //TODO: catch by ID? if (m_strGetLink==null) { Version.SoftVersion ver = Version.getSoftVersion(); if ( ver.nMajor > 4 ) m_strGetLink = RhoRuby.getMessageText("open_link_menu"); else m_strGetLink = RhoRuby.getMessageText("get_link_menu"); } if (m_strEmailMenu==null) m_strEmailMenu = RhoRuby.getMessageText("email_menu"); if (m_strCallMenu==null) m_strCallMenu = RhoRuby.getMessageText("call_menu"); Menu menu = _mainScreen.getMenu(0); int size = menu.getSize(); for(int i=0; i= 0; boolean bBB5 = strFullBrowser.indexOf("5") >= 0; if ( bTouch || bBB5 ) { if ( bTouch ) m_isFullBrowser = _mainScreen.isTouchScreen(); if (!m_isFullBrowser && bBB5 ) { Version.SoftVersion ver = Version.getSoftVersion(); m_isFullBrowser = ver.nMajor >= 5; } }else if ( RhoConf.getInstance().getBool("use_bb_full_browser") ) { Version.SoftVersion ver = Version.getSoftVersion(); if ( ver.nMajor > 4 || ( ver.nMajor == 4 && ver.nMinor >= 6 ) ) m_isFullBrowser = true; } if ( m_isFullBrowser ) { Version.SoftVersion ver = Version.getSoftVersion(); if ( ver.nMajor >= 5 ) m_oBrowserAdapter = new BrowserAdapter5(_mainScreen, this); else m_oBrowserAdapter = new BrowserAdapter(_mainScreen, this, RhoConf.getInstance().getBool("bb_loadimages_async") ); m_oBrowserAdapter.setFullBrowser(); }else m_oBrowserAdapter = new BrowserAdapter(_mainScreen, this, RhoConf.getInstance().getBool("bb_loadimages_async")); } private void invokeStartupWork() { // I think this can get called twice // 1) Directly from startup, if the app starts while the BB is up - e.g. after download // 2) From System Listener - after system restart and when the app is originally installed // To make sure we don't actually do the startup stuff twice, // we use _mainScreen as a flag if ( _mainScreen == null ) { if ( com.rho.Capabilities.RUNAS_SERVICE ) { LOG.INFO_OUT(" Shedule doStartupWork() ***---------------------------------- " ); this.invokeLater( new Runnable() { public void run() { doStartupWork(); } } ); }else doStartupWork(); } } //---------------------------------------------------------------------- // SystemListener methods public void powerUp() { LOG.INFO_OUT(" POWER UP ***----------------------------------*** " ); if ( com.rho.Capabilities.RUNAS_SERVICE) { invokeStartupWork(); this.requestBackground(); } } public void powerOff() { LOG.TRACE(" POWER DOWN ***----------------------------------*** " ); // _mainScreen = null; // doClose(); } public void batteryLow() { } public void batteryGood() { } public void batteryStatusChange(int status) { } // end of SystemListener methods //---------------------------------------------------------------------- private RhodesApplication() { LOG.INFO_OUT(" Construct RhodesApplication() ***----------------------------------*** " ); m_activateHooks = new Hashtable(); this.addSystemListener(this); //this.addFileSystemListener(this); if ( com.rho.Capabilities.RUNAS_SERVICE && ApplicationManager.getApplicationManager().inStartup() ) { LOG.INFO_OUT("We are in the phone startup, don't start Rhodes yet, leave it to power up call"); } else { invokeStartupWork(); } } public void refreshCurrentPage(){ navigateUrl((String)_history.lastElement()); } void navigateHome() { String strHomePage = RhoRuby.getStartPage(); String strStartPage = RhodesApp.getInstance().canonicalizeRhoUrl(strHomePage); _history.removeAllElements(); _history.addElement(strStartPage); navigateUrl(strStartPage); } public void close() { _mainScreen.close(); } public void processConnection(HttpConnection connection, Object e) { // cancel previous request /*if (_currentConnection != null) { try { _currentConnection.close(); } catch (IOException e1) { } } _currentConnection = connection;*/ RHODESAPP().getSplashScreen().hide(); m_oBrowserAdapter.processConnection(connection, e); } public static class PrimaryResourceFetchThread {//extends Thread { private static class HttpServerThread extends RhoThread { private Mutex m_mxStackCommands = new Mutex(); private LinkedList m_stackCommands = new LinkedList(); boolean m_bExit = false; private static final int INTERVAL_INFINITE = Integer.MAX_VALUE/1000; static final int WAIT_BEFOREKILL_SECONDS = 3; HttpServerThread() { super(new RhoClassFactory()); start(epNormal); } public void run() { LOG.INFO( "Starting HttpServerThread main routine..." ); //wait(80); try{ _application.initRuby(); }catch(Exception e) { LOG.ERROR("initRuby failed.", e); return; }catch(Throwable exc) { LOG.ERROR("initRuby crashed.", exc); return; } if (com.rho.Capabilities.ENABLE_PUSH) { _pushListeningThread = new PushListeningThread(); _pushListeningThread.start(); } while( !m_bExit ) { while(!m_stackCommands.isEmpty()) { PrimaryResourceFetchThread oCmd = null; synchronized(m_mxStackCommands) { oCmd = (PrimaryResourceFetchThread)m_stackCommands.removeFirst(); } try{ oCmd.processCommand(); }catch(Exception e) { LOG.ERROR("Process command failed.", e); }catch(Throwable exc) { LOG.ERROR("Process command crashed.", exc); } } wait(INTERVAL_INFINITE); } LOG.INFO( "Exit HttpServerThread main routine..." ); } void addCommand(PrimaryResourceFetchThread oCmd) { synchronized(m_mxStackCommands) { m_stackCommands.add(oCmd); } stopWait(); } }; private static HttpServerThread m_oFetchThread; private static RhodesApplication _application; private static Runnable _callback; private Object _event; private byte[] _postData; private HttpHeaders _requestHeaders; private String _url; private boolean m_bInternalRequest = false; private boolean m_bActivateApp = false; NetCallback m_netCallback; public void setInternalRequest(boolean b) { m_bInternalRequest = b; } public PrimaryResourceFetchThread(String url, HttpHeaders requestHeaders, byte[] postData, Object event) { _url = url; _requestHeaders = requestHeaders; _postData = postData; _event = event; //_callback = null; } public PrimaryResourceFetchThread(String url, HttpHeaders requestHeaders, byte[] postData, Object event, Runnable callback) { _url = url; _requestHeaders = requestHeaders; _postData = postData; _event = event; if ( callback != null ) _callback = callback; } public void setNetCallback(NetCallback netCallback) { m_netCallback = netCallback; m_bInternalRequest = true; } public PrimaryResourceFetchThread(boolean bActivateApp) { m_bActivateApp = bActivateApp; } static void Create(RhodesApplication app) { if ( m_oFetchThread != null ) return; _application = app; m_oFetchThread = new HttpServerThread(); } public void Destroy() { m_oFetchThread.m_bExit = true; m_oFetchThread.stop(HttpServerThread.WAIT_BEFOREKILL_SECONDS); m_oFetchThread = null; } public void start() { m_oFetchThread.addCommand(this); } void processCommand()throws IOException { if ( m_bActivateApp ) { RhoRuby.rho_ruby_activateApp(); return; } HttpConnection connection = Utilities.makeConnection(_url, _requestHeaders, _postData, null); if ( m_bInternalRequest ) { try{ int nRespCode = connection.getResponseCode(); if ( m_netCallback != null ) { String strRespBody = ""; InputStream is = connection.openInputStream(); if ( is != null ) { byte[] buffer = new byte[is.available()]; is.read(buffer); strRespBody = new String(buffer); } m_netCallback.setResponse( new NetResponse(strRespBody, nRespCode) ); } }catch(IOException exc) { LOG.ERROR("Callback failed: " + _url, exc); if ( m_netCallback != null ) m_netCallback.setResponse( new NetResponse("", 500) ); } } else { _application.processConnection(connection, _event); if (_callback != null ) { _callback.run(); _callback = null; } } } } }