Java tutorial
/** * The MIT License * ------------------------------------------------------------- * Copyright (c) 2008, Rob Ellis, Brock Whitten, Brian Leroux, Joe Bowser, Dave Johnson, Nitobi * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.phonegap; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import javax.microedition.io.HttpConnection; import net.rim.device.api.browser.field.BrowserContent; import net.rim.device.api.browser.field.BrowserContentChangedEvent; import net.rim.device.api.browser.field.Event; import net.rim.device.api.browser.field.RedirectEvent; import net.rim.device.api.browser.field.RenderingApplication; import net.rim.device.api.browser.field.RenderingException; import net.rim.device.api.browser.field.RenderingOptions; import net.rim.device.api.browser.field.RenderingSession; import net.rim.device.api.browser.field.RequestedResource; import net.rim.device.api.browser.field.SetHttpCookieEvent; import net.rim.device.api.browser.field.UrlRequestedEvent; import net.rim.device.api.io.http.HttpHeaders; import net.rim.device.api.system.Application; 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.ui.Field; import net.rim.device.api.ui.UiApplication; import net.rim.device.api.ui.component.BitmapField; import net.rim.device.api.ui.component.Status; import net.rim.device.api.ui.container.MainScreen; import com.phonegap.api.CommandManager; import com.phonegap.api.CommandResult; import com.phonegap.io.ConnectionManager; import com.phonegap.io.PrimaryResourceFetchThread; import com.phonegap.io.SecondaryResourceFetchThread; import org.json.me.*; /** * Bridges HTML/JS/CSS to a native Blackberry application. * @author Jose Noheda * @author Fil Maj * @author Dave Johnson */ public class PhoneGap extends UiApplication implements RenderingApplication { public static final String APPLICATION_UID = "%PLACEHOLDER%"; // Gets replaced at build-time with the app name; used as a key to reference offline storage. CAVEAT: if you change your app name, you will lose any existing offline data. public static final String PHONEGAP_PROTOCOL = "PhoneGap="; private static final String DEFAULT_INITIAL_URL = "data:///www/index.html"; private static final String LOADING_IMAGE = "www/Default.png"; private static final String REFERER = "referer"; private static final String REDIRECT_MSG = "You are being redirected to a different page..."; public Vector pendingResponses = new Vector(); private CommandManager commandManager; private RenderingSession _renderingSession; public HttpConnection _currentConnection; private MainScreen _mainScreen; private EncodedImage loadingImage; private BitmapField loadingField = new BitmapField(); private MainScreen loadingScreen = new MainScreen(); private Timer refreshTimer; /** * Launches the application. Accepts up to one parameter, a URL to the index page. */ public static void main(String[] args) { PhoneGap bridge = args.length > 0 ? new PhoneGap(args[0]) : new PhoneGap(); bridge.enterEventDispatcher(); } public PhoneGap() { init(DEFAULT_INITIAL_URL); } /** * Launches the application with a custom index page. * * @param url a http:// or data:// string */ public PhoneGap(final String url) { if ((url != null) && (url.trim().length() > 0)) { init(url); } else { init(DEFAULT_INITIAL_URL); } } private void init(final String url) { commandManager = new CommandManager(this); _mainScreen = new MainScreen(); pushScreen(_mainScreen); // Add loading screen and display ASAP loadingImage = EncodedImage.getEncodedImageResource(LOADING_IMAGE); if (loadingImage != null) { // If a loading image exists, add it to the loading field and push it onto the screen stack. loadingField.setImage(loadingImage); loadingScreen.add(loadingField); pushScreen(loadingScreen); } _mainScreen.addKeyListener(new PhoneGapKeyListener(this)); // Set up the browser/renderer. _renderingSession = RenderingSession.getNewInstance(); _renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.JAVASCRIPT_ENABLED, true); _renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.JAVASCRIPT_LOCATION_ENABLED, true); // Enable nice-looking BlackBerry browser field. _renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, 17000, true); PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread(url, null, null, null, this); thread.start(); refreshTimer = new Timer(); refreshTimer.scheduleAtFixedRate(new TimerRefresh(), 500, 500); } public Object eventOccurred(final Event event) { int eventId = event.getUID(); switch (eventId) { case Event.EVENT_REDIRECT: { RedirectEvent e = (RedirectEvent) event; String referrer = e.getSourceURL(); switch (e.getType()) { case RedirectEvent.TYPE_SINGLE_FRAME_REDIRECT: // Show redirect message. Application.getApplication().invokeAndWait(new Runnable() { public void run() { Status.show(REDIRECT_MSG); } }); break; case RedirectEvent.TYPE_JAVASCRIPT: break; case RedirectEvent.TYPE_META: // MSIE and Mozilla don't send a Referer for META Refresh. referrer = null; break; case RedirectEvent.TYPE_300_REDIRECT: // MSIE, Mozilla, and Opera all send the original request's Referer as the Referer for the new request. Object eventSource = e.getSource(); if (eventSource instanceof HttpConnection) { referrer = ((HttpConnection) eventSource).getRequestProperty(REFERER); } eventSource = null; break; } this.showLoadingScreen(); // Create the request, populate header with referrer and fire off the request. HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setProperty(REFERER, referrer); PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread(e.getLocation(), requestHeaders, null, event, this); thread.start(); e = null; referrer = null; requestHeaders = null; break; } case Event.EVENT_URL_REQUESTED: { this.showLoadingScreen(); UrlRequestedEvent urlRequestedEvent = (UrlRequestedEvent) event; String url = urlRequestedEvent.getURL(); HttpHeaders header = urlRequestedEvent.getHeaders(); PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread(url, header, urlRequestedEvent.getPostData(), event, this); thread.start(); urlRequestedEvent = null; url = null; header = null; break; } case Event.EVENT_BROWSER_CONTENT_CHANGED: { // Browser field title might have changed update title. BrowserContentChangedEvent browserContentChangedEvent = (BrowserContentChangedEvent) event; if (browserContentChangedEvent.getSource() instanceof BrowserContent) { BrowserContent browserField = (BrowserContent) browserContentChangedEvent.getSource(); String newTitle = browserField.getTitle(); if (newTitle != null) { synchronized (getAppEventLock()) { _mainScreen.setTitle(newTitle); } } browserField = null; newTitle = null; } browserContentChangedEvent = null; break; } case Event.EVENT_CLOSE: // TODO: close the application break; case Event.EVENT_SET_HEADER: // No cache support. case Event.EVENT_SET_HTTP_COOKIE: String cookie = ((SetHttpCookieEvent) event).getCookie(); // Support the old protocol still if (cookie.startsWith(PHONEGAP_PROTOCOL)) { String response = commandManager.processInstruction(cookie); if ((response != null) && (response.trim().length() > 0)) { pendingResponses.addElement(response); } response = null; } else { // cookie = {clazz:'com.example.Foo', action:'bar', callbackId:'1', args:{...}, async:true}; try { JSONObject o = new JSONObject(cookie); JSONArray args = o.getJSONArray("args"); String clazz = o.getString("clazz"); String action = o.getString("action"); String callbackId = o.getString("callbackId"); boolean async = o.getBoolean("async"); String response = commandManager.exec(clazz, action, callbackId, args, async); if ((response != null) && (response.trim().length() > 0)) { pendingResponses.addElement(response); } response = null; } catch (JSONException e) { pendingResponses .addElement(new CommandResult(CommandResult.Status.JSON_EXCEPTION, "").toErrorString()); } } cookie = null; break; case Event.EVENT_HISTORY: // TODO: No history support.. but we added our own history stack implementation in ConnectionManager. Can we hook it up - then we'd have access to window.history :o case Event.EVENT_EXECUTING_SCRIPT: // No progress bar is supported. case Event.EVENT_FULL_WINDOW: // No full window support. case Event.EVENT_STOP: // No stop loading support. default: } return null; } /** * Catch the 'get' cookie event, aggregate PhoneGap API responses that haven't been flushed and return. **/ public String getHTTPCookie(String url) { StringBuffer responseCode = new StringBuffer(); synchronized (pendingResponses) { for (int index = 0; index < pendingResponses.size(); index++) responseCode.append(pendingResponses.elementAt(index)); pendingResponses.removeAllElements(); } return responseCode.toString(); } public int getAvailableHeight(BrowserContent browserContent) { return Display.getHeight(); } public int getAvailableWidth(BrowserContent browserContent) { return Display.getWidth(); } public int getHistoryPosition(BrowserContent browserContent) { return 0; // TODO: No support... but should try hooking it up to our own implementation of the history stack and see what happens. } public HttpConnection getResource(RequestedResource resource, BrowserContent referrer) { if ((resource != null) && (resource.getUrl() != null) && !resource.isCacheOnly()) { String url = resource.getUrl().trim(); if ((referrer == null) || (ConnectionManager.isInternal(url, resource))) { return ConnectionManager.getUnmanagedConnection(url, resource.getRequestHeaders(), null); } else { SecondaryResourceFetchThread.enqueue(resource, referrer); } url = null; } return null; } public void showLoadingScreen() { synchronized (Application.getEventLock()) { pushScreen(this.loadingScreen); } } public void hideLoadingScreen() { synchronized (Application.getEventLock()) { try { popScreen(this.loadingScreen); } catch (Exception e) { } } } /** * Processes a new HttpConnection object to instantiate a new browser Field (aka WebView) object, and then resets the screen to the newly-created Field. * @param connection * @param e */ public void processConnection(HttpConnection connection, Event e) { // Cancel previous request. if (_currentConnection != null) { try { _currentConnection.close(); } catch (IOException e1) { } } // Clear out pending responses. synchronized (pendingResponses) { pendingResponses.removeAllElements(); } // Cancel any XHRs happening. commandManager.stopXHR(); _currentConnection = connection; BrowserContent browserContent = null; Field field = null; try { browserContent = _renderingSession.getBrowserContent(connection, this, e); if (browserContent != null) { field = browserContent.getDisplayableContent(); if (field != null) { synchronized (Application.getEventLock()) { // The deleteAll call will remove the loading screen if exists. _mainScreen.deleteAll(); _mainScreen.add(field); } } browserContent.finishLoading(); } } catch (RenderingException re) { } finally { browserContent = null; field = null; this.hideLoadingScreen(); // Manually call the garbage collector to clean up all of leftover objects and free up the nulled object handles. System.gc(); } } /** * Required for implementing RenderingApplication ... but not used. Use invokeLater instead. */ public void invokeRunnable(Runnable runnable) { (new Thread(runnable)).start(); } /** * This is essentially to make the API more like Android. * * @param javascript */ public void loadUrl(String javascript) { System.out.println(javascript); pendingResponses.addElement(javascript); } /** * An analogous function to String.replaceAll from J2SE, but unavailable on Micro. Courtesy of Jijo from http://www.itgalary.com/forum_posts.asp?TID=871 * @param _text * @return */ public static final String replace(String _text, String _searchStr, String _replacementStr) { StringBuffer sb = new StringBuffer(); int searchStringPos = _text.indexOf(_searchStr); int startPos = 0; int searchStringLength = _searchStr.length(); while (searchStringPos != -1) { sb.append(_text.substring(startPos, searchStringPos)).append(_replacementStr); startPos = searchStringPos + searchStringLength; searchStringPos = _text.indexOf(_searchStr, startPos); } sb.append(_text.substring(startPos, _text.length())); return sb.toString(); } public static final String[] splitString(final String data, final char splitChar, final boolean allowEmpty) { Vector v = new Vector(); int indexStart = 0; int indexEnd = data.indexOf(splitChar); if (indexEnd != -1) { while (indexEnd != -1) { String s = data.substring(indexStart, indexEnd); if (allowEmpty || s.length() > 0) { v.addElement(s); } s = null; indexStart = indexEnd + 1; indexEnd = data.indexOf(splitChar, indexStart); } if (indexStart != data.length()) { // Add the rest of the string String s = data.substring(indexStart); if (allowEmpty || s.length() > 0) { v.addElement(s); } s = null; } } else { if (allowEmpty || data.length() > 0) { v.addElement(data); } } String[] result = new String[v.size()]; v.copyInto(result); v = null; return result; } private class TimerRefresh extends TimerTask { public void run() { UiApplication.getUiApplication().invokeLater(new Runnable() { public void run() { int numFields = _mainScreen.getFieldCount(); for (int i = 0; i < numFields; i++) { Field field = _mainScreen.getField(i); field.getManager().invalidate(); field = null; } _mainScreen.doPaint(); } }); } } private static class PhoneGapKeyListener implements KeyListener { private PhoneGap phoneGap; public PhoneGapKeyListener(PhoneGap pg) { phoneGap = pg; } public boolean keyChar(char arg0, int arg1, int arg2) { // Catch BlackBerry's back key, pop history URL stack and initiate HTTP request to it. if (ConnectionManager.history.size() > 1 && arg0 == Characters.ESCAPE) { phoneGap.showLoadingScreen(); ConnectionManager.history.pop(); PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread( (String) ConnectionManager.history.pop(), null, null, null, this.phoneGap); thread.start(); return true; } return false; } public boolean keyDown(int keycode, int time) { 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; } } }