diff --git a/.gitignore b/.gitignore index f8359345..36522045 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,3 @@ -.DS_Store -.*.sw? -*.cso -tmp/ -*.xcodeproj/* !*.xcodeproj/project.pbxproj -lib/iphone/phonegap-min.js -lib/iphone/phonegap.js -android/bin -android/gen \ No newline at end of file +android/default.properties +android/bin/* +android/gen/* \ No newline at end of file diff --git a/blackberry/.gitignore b/blackberry/.gitignore index e69de29b..e457492f 100644 --- a/blackberry/.gitignore +++ b/blackberry/.gitignore @@ -0,0 +1,4 @@ +.tmp +.settings +bin/* +.project.old \ No newline at end of file diff --git a/blackberry/phonegap.jdp b/blackberry/phonegap.jdp index 5ed95231..24d5375c 100644 --- a/blackberry/phonegap.jdp +++ b/blackberry/phonegap.jdp @@ -21,80 +21,35 @@ AutoRestart=0 ] ExcludeFromBuildAll=0 [Files -..\..\..\..\workspace_blackberry\phonegap\src\0.png -..\..\..\..\workspace_blackberry\phonegap\src\1.png -..\..\..\..\workspace_blackberry\phonegap\src\2.png -..\..\..\..\workspace_blackberry\phonegap\src\3.png -..\..\..\..\workspace_blackberry\phonegap\src\4.png -..\..\..\..\workspace_blackberry\phonegap\src\5.png -..\..\..\..\workspace_blackberry\phonegap\src\camera.html -..\..\..\..\workspace_blackberry\phonegap\src\camera.js -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\api\Command.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\api\CommandManager.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\api\impl\CameraCommand.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\api\impl\ContactsCommand.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\api\impl\GeoLocationCommand.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\api\impl\InitializationCommand.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\api\impl\TelephonyCommand.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\api\impl\VibrationCommand.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\io\AsynchronousResourceFetcher.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\io\Callback.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\io\ConnectionManager.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\io\QueueResourceFetcher.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\model\Position.java -..\..\..\..\workspace_blackberry\phonegap\src\com\nitobi\phonegap\PhoneGap.java -..\..\..\..\workspace_blackberry\phonegap\src\contacts.html -..\..\..\..\workspace_blackberry\phonegap\src\contacts.js -..\..\..\..\workspace_blackberry\phonegap\src\device.html -..\..\..\..\workspace_blackberry\phonegap\src\device.js -..\..\..\..\workspace_blackberry\phonegap\src\geolocation.js -..\..\..\..\workspace_blackberry\phonegap\src\index.html -..\..\..\..\workspace_blackberry\phonegap\src\io.html -..\..\..\..\workspace_blackberry\phonegap\src\location.html -..\..\..\..\workspace_blackberry\phonegap\src\position.js -..\..\..\..\workspace_blackberry\phonegap\src\telephony.html -..\..\..\..\workspace_blackberry\phonegap\src\telephony.js -..\..\..\..\workspace_blackberry\phonegap\src\vibration.html -..\..\..\..\workspace_blackberry\phonegap\src\www\js\camera.js -..\..\..\..\workspace_blackberry\phonegap\src\www\js\contacts.js -..\..\..\..\workspace_blackberry\phonegap\src\www\js\device.js -..\..\..\..\workspace_blackberry\phonegap\src\www\js\geolocation.js -..\..\..\..\workspace_blackberry\phonegap\src\www\js\position.js -..\..\..\..\workspace_blackberry\phonegap\src\www\js\telephony.js -..\..\..\..\workspace_blackberry\phonegap\src\www\test\camera.html -..\..\..\..\workspace_blackberry\phonegap\src\www\test\contacts.html -..\..\..\..\workspace_blackberry\phonegap\src\www\test\device.html -..\..\..\..\workspace_blackberry\phonegap\src\www\test\images\0.png -..\..\..\..\workspace_blackberry\phonegap\src\www\test\images\1.png -..\..\..\..\workspace_blackberry\phonegap\src\www\test\images\2.png -..\..\..\..\workspace_blackberry\phonegap\src\www\test\images\3.png -..\..\..\..\workspace_blackberry\phonegap\src\www\test\images\4.png -..\..\..\..\workspace_blackberry\phonegap\src\www\test\images\5.png -..\..\..\..\workspace_blackberry\phonegap\src\www\test\index.html -..\..\..\..\workspace_blackberry\phonegap\src\www\test\io.html -..\..\..\..\workspace_blackberry\phonegap\src\www\test\location.html -..\..\..\..\workspace_blackberry\phonegap\src\www\test\telephony.html -..\..\..\..\workspace_blackberry\phonegap\src\www\test\vibration.html src\com\nitobi\phonegap\api\Command.java src\com\nitobi\phonegap\api\CommandManager.java src\com\nitobi\phonegap\api\impl\CameraCommand.java src\com\nitobi\phonegap\api\impl\ContactsCommand.java +src\com\nitobi\phonegap\api\impl\DeviceCommand.java src\com\nitobi\phonegap\api\impl\GeoLocationCommand.java -src\com\nitobi\phonegap\api\impl\InitializationCommand.java +src\com\nitobi\phonegap\api\impl\MediaCommand.java +src\com\nitobi\phonegap\api\impl\NotificationCommand.java src\com\nitobi\phonegap\api\impl\TelephonyCommand.java -src\com\nitobi\phonegap\api\impl\VibrationCommand.java src\com\nitobi\phonegap\io\AsynchronousResourceFetcher.java src\com\nitobi\phonegap\io\Callback.java src\com\nitobi\phonegap\io\ConnectionManager.java +src\com\nitobi\phonegap\io\HttpConnection.java +src\com\nitobi\phonegap\io\PrimaryResourceFetchThread.java src\com\nitobi\phonegap\io\QueueResourceFetcher.java +src\com\nitobi\phonegap\io\SecondaryResourceFetchThread.java src\com\nitobi\phonegap\model\Position.java src\com\nitobi\phonegap\PhoneGap.java +src\www\css\test.css src\www\js\camera.js src\www\js\contacts.js src\www\js\device.js src\www\js\geolocation.js +src\www\js\media.js +src\www\js\notification.js src\www\js\position.js src\www\js\telephony.js +src\www\media\bird.mp3 +src\www\media\percBass2.wav src\www\test\camera.html src\www\test\contacts.html src\www\test\device.html @@ -107,8 +62,9 @@ src\www\test\images\5.png src\www\test\index.html src\www\test\io.html src\www\test\location.html +src\www\test\media.html +src\www\test\notification.html src\www\test\telephony.html -src\www\test\vibration.html ] HaveAlxImports=0 HaveDefs=0 @@ -120,9 +76,8 @@ HaveImports=0 [Imports ] Listing=0 -MidletClass=data:///www/test/index.html Options=-quiet -OutputFileName=phonegap +OutputFileName=PhoneGapBB [PackageProtection ] RibbonPosition=0 @@ -131,6 +86,5 @@ RibbonPosition=0 RunOnStartup=0 StartupTier=7 SystemModule=0 -Title=PhoneGap Type=0 UserData=|src diff --git a/blackberry/src/com/nitobi/phonegap/PhoneGap.java b/blackberry/src/com/nitobi/phonegap/PhoneGap.java index 4010d4e1..147bff19 100644 --- a/blackberry/src/com/nitobi/phonegap/PhoneGap.java +++ b/blackberry/src/com/nitobi/phonegap/PhoneGap.java @@ -22,46 +22,51 @@ */ package com.nitobi.phonegap; +import java.io.IOException; import java.util.Vector; import javax.microedition.io.HttpConnection; import net.rim.device.api.browser.field.BrowserContent; -import net.rim.device.api.browser.field.BrowserContentManager; +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.Display; -import net.rim.device.api.ui.Screen; +import net.rim.device.api.ui.Field; import net.rim.device.api.ui.UiApplication; +import net.rim.device.api.ui.component.Status; import net.rim.device.api.ui.container.MainScreen; import com.nitobi.phonegap.api.CommandManager; -import com.nitobi.phonegap.io.AsynchronousResourceFetcher; -import com.nitobi.phonegap.io.Callback; import com.nitobi.phonegap.io.ConnectionManager; -import com.nitobi.phonegap.io.QueueResourceFetcher; +import com.nitobi.phonegap.io.PrimaryResourceFetchThread; +import com.nitobi.phonegap.io.SecondaryResourceFetchThread; /** * 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 PHONEGAP_PROTOCOL = "gap://"; + public static final String PHONEGAP_PROTOCOL = "PhoneGap="; private static final String DEFAULT_INITIAL_URL = "data:///www/test/index.html"; - - private Screen mainScreen; + private static final String REFERER = "referer"; private Vector pendingResponses = new Vector(); - private QueueResourceFetcher queueResourceFetcher; private CommandManager commandManager = new CommandManager(); - private ConnectionManager connectionManager = new ConnectionManager(); - private BrowserContentManager _browserContentManager = new BrowserContentManager(0); + private RenderingSession _renderingSession; + public HttpConnection _currentConnection; + private MainScreen _mainScreen; /** * Launches the application. Accepts up to one parameter, an URL to the index page. @@ -88,49 +93,120 @@ public PhoneGap(final String url) { } private void init(final String url) { - RenderingOptions renderingOptions = _browserContentManager.getRenderingSession().getRenderingOptions(); - renderingOptions.setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.JAVASCRIPT_ENABLED, true); - renderingOptions.setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.JAVASCRIPT_LOCATION_ENABLED, true); - // The following line is commented out for now, until we figure out how to use this rendering mode properly. - //renderingOptions.setProperty(RenderingOptions.CORE_OPTIONS_GUID, 17000, true); - mainScreen = new MainScreen(); - mainScreen.add(_browserContentManager); - pushScreen(mainScreen); - queueResourceFetcher = new QueueResourceFetcher(this, connectionManager); - loadUrl(url); - invokeLater(queueResourceFetcher); - } - - private void loadUrl(String url) { - invokeAndWait(new AsynchronousResourceFetcher(url, new Callback() { - public void execute(final Object input) { - // setContent here causes Blackberry to freeze - leads to two calls: handleNewContent and finishLoading. - // finishLoading then calls Object.wait(), which is likely the cause for the freeze? Help! - _browserContentManager.setContent((HttpConnection) input, PhoneGap.this, null); - } - }, connectionManager)); - } - - public Object eventOccurred(final Event event) { - if (event instanceof RedirectEvent) { - RedirectEvent command = (RedirectEvent) event; - String url = command.getLocation(); - if (url.startsWith(PHONEGAP_PROTOCOL)) { - String response = commandManager.processInstruction(url); - if ((response != null) && (response.trim().length() > 0)) pendingResponses.addElement(response); - } - } - if (event instanceof UrlRequestedEvent) { - final String url = ((UrlRequestedEvent) event).getURL(); - new Thread(new AsynchronousResourceFetcher(url, new Callback() { - public void execute(final Object input) { - _browserContentManager.setContent((HttpConnection) input, PhoneGap.this, null); - } - }, connectionManager)).start(); - } - return null; + _mainScreen = new MainScreen(); + pushScreen(_mainScreen); + _renderingSession = RenderingSession.getNewInstance(); + + // Enable JavaScript. + _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 BB browser field. + _renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, 17000, true); + PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread(url, null, null, null, this); + thread.start(); } + public Object eventOccurred(final Event event) + { + int eventId = event.getUID(); + switch (eventId) + { + case Event.EVENT_REDIRECT : + { + RedirectEvent e = (RedirectEvent) event; + String url = e.getLocation(); + 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("You are being redirected to a different page..."); + } + }); + break; + + case RedirectEvent.TYPE_JAVASCRIPT : + String test = "test"; + 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); + } + + break; + } + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.setProperty(REFERER, referrer); + PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread(e.getLocation(), requestHeaders,null, event, this); + thread.start(); + break; + } + case Event.EVENT_URL_REQUESTED : + { + UrlRequestedEvent urlRequestedEvent = (UrlRequestedEvent) event; + String url = urlRequestedEvent.getURL(); + HttpHeaders header = urlRequestedEvent.getHeaders(); + PrimaryResourceFetchThread thread = new PrimaryResourceFetchThread( + url, header, urlRequestedEvent.getPostData(), event, this); + thread.start(); + 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); + } + } + } + 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(); + if (cookie.startsWith(PHONEGAP_PROTOCOL)) { + String response = commandManager.processInstruction(cookie); + if ((response != null) && (response.trim().length() > 0)) pendingResponses.addElement(response); + } + break; + case Event.EVENT_HISTORY : // No history support. + 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) { @@ -156,16 +232,98 @@ public int getHistoryPosition(BrowserContent browserContent) { 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))) - return connectionManager.getUnmanagedConnection(url); + if ((referrer == null) || (ConnectionManager.isInternal(url))) + return ConnectionManager.getUnmanagedConnection(url, resource.getRequestHeaders(), null); else - queueResourceFetcher.enqueue(resource, referrer); + SecondaryResourceFetchThread.enqueue(resource, referrer); } return null; } + /** + * 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) + { + } + } + _currentConnection = connection; + BrowserContent browserContent = null; + try + { + browserContent = _renderingSession.getBrowserContent(connection, this, e); + if (browserContent != null) + { + Field field = browserContent.getDisplayableContent(); + if (field != null) + { + synchronized (Application.getEventLock()) + { + _mainScreen.deleteAll(); + _mainScreen.add(field); + } + } + + browserContent.finishLoading(); + } + } + catch (RenderingException re) + { + } + } + public void invokeRunnable(Runnable runnable) + { + (new Thread(runnable)).start(); + } + public static final String[] splitString(final String data, final char splitChar, final boolean allowEmpty) + { + Vector v = new Vector(); - public void invokeRunnable(Runnable runnable) { - invokeLater(runnable); - } + 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); + } + 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); + } + } + } + else + { + if (allowEmpty || data.length() > 0) + { + v.addElement(data); + } + } + String[] result = new String[v.size()]; + v.copyInto(result); + return result; + } } diff --git a/blackberry/src/com/nitobi/phonegap/api/CommandManager.java b/blackberry/src/com/nitobi/phonegap/api/CommandManager.java index a8bf96be..d008d28c 100644 --- a/blackberry/src/com/nitobi/phonegap/api/CommandManager.java +++ b/blackberry/src/com/nitobi/phonegap/api/CommandManager.java @@ -24,10 +24,11 @@ import com.nitobi.phonegap.api.impl.CameraCommand; import com.nitobi.phonegap.api.impl.ContactsCommand; +import com.nitobi.phonegap.api.impl.DeviceCommand; import com.nitobi.phonegap.api.impl.GeoLocationCommand; -import com.nitobi.phonegap.api.impl.InitializationCommand; +import com.nitobi.phonegap.api.impl.MediaCommand; +import com.nitobi.phonegap.api.impl.NotificationCommand; import com.nitobi.phonegap.api.impl.TelephonyCommand; -import com.nitobi.phonegap.api.impl.VibrationCommand; /** * Given a execution request detects matching {@link Command} and executes it. @@ -38,15 +39,16 @@ public final class CommandManager { // List of installed Commands - private Command[] commands = new Command[6]; + private Command[] commands = new Command[7]; public CommandManager() { commands[0] = new CameraCommand(); commands[1] = new ContactsCommand(); - commands[2] = new VibrationCommand(); + commands[2] = new NotificationCommand(); commands[3] = new TelephonyCommand(); commands[4] = new GeoLocationCommand(); - commands[5] = new InitializationCommand(); + commands[5] = new DeviceCommand(); + commands[6] = new MediaCommand(); } /** diff --git a/blackberry/src/com/nitobi/phonegap/api/impl/CameraCommand.java b/blackberry/src/com/nitobi/phonegap/api/impl/CameraCommand.java index 5ee18c01..b1be687c 100644 --- a/blackberry/src/com/nitobi/phonegap/api/impl/CameraCommand.java +++ b/blackberry/src/com/nitobi/phonegap/api/impl/CameraCommand.java @@ -35,7 +35,7 @@ import com.nitobi.phonegap.api.Command; /** - * Switchs current application to the camera to take a photo. + * Switches current application to the camera to take a photo. * * @author Jose Noheda * @@ -44,7 +44,8 @@ public class CameraCommand implements Command { private static final int INVOKE_COMMAND = 0; private static final int PICTURE_COMMAND = 1; - private static final String CODE = "gap://camera"; + private static final String CODE = "PhoneGap=camera"; + //private static final String private long lastUSN = 0; private String photoPath; @@ -70,10 +71,6 @@ public void fileJournalChanged() { } }; } - - /** - * Able to run the camera command. Ex: gap://camera/obtain - */ public boolean accept(String instruction) { return instruction != null && instruction.startsWith(CODE); } @@ -85,7 +82,7 @@ public String execute(String instruction) { switch (getCommand(instruction)) { case PICTURE_COMMAND: UiApplication.getUiApplication().removeFileSystemJournalListener(listener); - return "navigator.camera.picture = '" + photoPath + "'"; + return ";navigator.camera.picture = '" + photoPath + "';"; case INVOKE_COMMAND: photoPath = null; UiApplication.getUiApplication().addFileSystemJournalListener(listener); diff --git a/blackberry/src/com/nitobi/phonegap/api/impl/ContactsCommand.java b/blackberry/src/com/nitobi/phonegap/api/impl/ContactsCommand.java index 23323dc7..0e70e54e 100644 --- a/blackberry/src/com/nitobi/phonegap/api/impl/ContactsCommand.java +++ b/blackberry/src/com/nitobi/phonegap/api/impl/ContactsCommand.java @@ -23,29 +23,34 @@ package com.nitobi.phonegap.api.impl; import java.util.Enumeration; +import java.util.Hashtable; import javax.microedition.pim.Contact; import javax.microedition.pim.PIM; - +import javax.microedition.pim.PIMException; import net.rim.blackberry.api.pdap.BlackBerryContact; import net.rim.blackberry.api.pdap.BlackBerryContactList; +import com.nitobi.phonegap.PhoneGap; import com.nitobi.phonegap.api.Command; /** * Finds data in agenda. * * @author Jose Noheda + * @author Fil Maj * */ public class ContactsCommand implements Command { private static final int SEARCH_COMMAND = 0; - private static final String CODE = "gap://contacts"; + private static final int GET_ALL_COMMAND = 1; + private static final int CHOOSE_COMMAND = 2; + private static final int REMOVE_COMMAND = 3; + private static final int NEW_COMMAND = 4; + private static final String CODE = "PhoneGap=contacts"; + private static final String CONTACT_MANAGER_JS_NAMESPACE = "navigator.ContactManager"; - /** - * Able to run the call command. Ex: gap://contacts/search/name/Joe - */ public boolean accept(String instruction) { return instruction != null && instruction.startsWith(CODE); } @@ -54,39 +59,224 @@ public boolean accept(String instruction) { * Invokes internal phone application. */ public String execute(String instruction) { + Hashtable options = ContactsCommand.parseParameters(instruction); switch (getCommand(instruction)) { case SEARCH_COMMAND: - return "navigator.ContactManager.contacts = navigator.ContactManager.contacts.concat(" + getAgendaByName("Joe") + ");"; + return getAgenda(options); + case GET_ALL_COMMAND: + return getAgenda(options); + case CHOOSE_COMMAND: + return chooseContact(); + case REMOVE_COMMAND: + return removeContact(options); + case NEW_COMMAND: + return newContact(options); } return null; } - + /** + * Parses the options object and returns a hash of params. + * @param instruction The cookie/string representation of the instruction. + * @return Hashtable Hash of key:value pairs containing the parameter names & values. + */ + private static Hashtable parseParameters(String instruction) { + String[] params = PhoneGap.splitString(instruction, '/', false); + int numParams = params.length; + Hashtable hash = new Hashtable(); + for (int i = 0; i < numParams; i++) { + String curParam = params[i]; + if (curParam.indexOf(':') == -1) continue; + String[] key_value = PhoneGap.splitString(curParam, ':', false); + if (key_value.length < 2) continue; + String key = key_value[0]; + String value = key_value[1]; + hash.put(key, value); + } + return hash; + } private int getCommand(String instruction) { - String command = instruction.substring(instruction.substring(7).indexOf('/') + 1); - if (command.indexOf("search") > 0) return SEARCH_COMMAND; + String command = instruction.substring(instruction.indexOf('/') + 1); + if (command.startsWith("search")) return SEARCH_COMMAND; + if (command.startsWith("getall")) return GET_ALL_COMMAND; + if (command.startsWith("choose")) return CHOOSE_COMMAND; + if (command.startsWith("remove")) return REMOVE_COMMAND; + if (command.startsWith("new")) return NEW_COMMAND; return -1; } + /** + * Creates a new contact based on the hash of parameters passed in via options. + * @param options Parsed parameters for use with creating a new contact. + * @return String, which will be executed back in browser. Just callback invokes. + */ + private String newContact(Hashtable options) { + try { + BlackBerryContactList agenda = (BlackBerryContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE); + BlackBerryContact contact = (BlackBerryContact) agenda.createContact(); + // Add name(s). + String[] nameField = new String[2]; + nameField[Contact.NAME_FAMILY] = options.get("lastName").toString(); + nameField[Contact.NAME_GIVEN] = options.get("firstName").toString(); + if (agenda.isSupportedField(Contact.NAME)) contact.addStringArray(Contact.NAME, Contact.ATTR_NONE, nameField); + // TODO: Need to finalize JSON representation of address - it's multi-field in BlackBerry :s. + + // TODO: Figure out how attributes and fields work for contact in BlackBerry. RUN TESTS! Code below may change. + String numbers = options.get("phoneNumber").toString(); + if (agenda.isSupportedField(Contact.TEL)) contact.addString(Contact.TEL, Contact.ATTR_MOBILE, numbers.substring(numbers.lastIndexOf('=')+1)); + String emails = options.get("email").toString(); + if (agenda.isSupportedField(Contact.EMAIL)) contact.addString(Contact.EMAIL, Contact.ATTR_MOBILE, emails.substring(emails.lastIndexOf('=')+1)); + contact.commit(); + return ";if (" + CONTACT_MANAGER_JS_NAMESPACE + ".new_onSuccess) { " + CONTACT_MANAGER_JS_NAMESPACE + ".new_onSuccess(); };"; + } catch (PIMException e) { + e.printStackTrace(); + return ";if (" + CONTACT_MANAGER_JS_NAMESPACE + ".new_onError) { " + CONTACT_MANAGER_JS_NAMESPACE + ".new_onError(); };"; + } + } - private String getAgendaByName(String name) { + /** + * Removes the specified contact from the contact list. + * @param options A hash of options (parameters) passed by the PhoneGap app. Needs to contain a 'contactID' property for the removal to go through properly. + * @return JavaScript that will be evaluated by the PhoneGap app - only callbacks. + */ + private String removeContact(Hashtable options) { + if (options.contains("contactID")) { + try { + BlackBerryContactList agenda = (BlackBerryContactList) PIM + .getInstance().openPIMList(PIM.CONTACT_LIST, + PIM.READ_WRITE); + Contact matchContact = agenda.createContact(); + int contactID = Integer.parseInt(options.get("contactID").toString()); + if (agenda.isSupportedField(Contact.UID)) matchContact.addInt(Contact.UID, Contact.ATTR_HOME | Contact.ATTR_PREFERRED, contactID); + Enumeration matches = agenda.items(matchContact); + if (matches.hasMoreElements()) { + // Matched to a contact. + } else { + // No matches found - call error callback. + + } + } catch (Exception e) { + e.printStackTrace(); + // Trigger error callback if exception occurs. + return ";if (" + CONTACT_MANAGER_JS_NAMESPACE + ".remove_onError) { " + CONTACT_MANAGER_JS_NAMESPACE + ".remove_onError(); };"; + } + } else { + return ";alert('[PhoneGap Error] Contact ID not specified during contact removal operation.');"; + } + return null; + } + /** + * Invokes the default BlackBerry contact chooser to allow the user to choose a contact. + * @return JSON representation of the chosen contact, which will then be sent back to JavaScript. + */ + private String chooseContact() { try { BlackBerryContactList agenda = (BlackBerryContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_ONLY); + BlackBerryContact blackberryContact; + StringBuffer contacts = new StringBuffer("["); if (agenda != null) { - StringBuffer contacts = new StringBuffer("["); - Enumeration matches = agenda.itemsByName(name); - while (matches.hasMoreElements()) { - BlackBerryContact contact = (BlackBerryContact) matches.nextElement(); - contacts.append("{email:'"); - contacts.append(contact.getString(Contact.EMAIL, 0)); - contacts.append("', phone:'"); - contacts.append(contact.getString(Contact.TEL, 0)); - contacts.append("'},"); + blackberryContact = (BlackBerryContact) agenda.choose(); + agenda.close(); + ContactsCommand.addContactToBuffer(contacts, blackberryContact); + contacts.append("];"); + return ";" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".contacts=" + contacts.toString() + "if (" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".choose_onSuccess) { " + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".choose_onSuccess();" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".choose_onSuccess = null; };"; + } else { + // TODO: If cannot get reference to Agenda, should the error or success callback be called? + return ";" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".contacts=" + contacts.append("];").toString() + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".choose_onSuccess = null;"; + } + } catch (Exception e) { + System.out.println("Exception getting contact list: " + e.getMessage()); + // TODO: No error callbacks associated with contact chooser - what to do? + } + return null; + } + /** + * Returns a contact list, either all contacts or contacts matching the optional search parameter. + * @param options A hash of options to pass into retrieving contacts. These can include name filters and paging parameters. + * @return JSON string representing the contacts that are retrieved, plus necessary JavaScript callbacks. + */ + private String getAgenda(Hashtable options) { + String callbackHook = ""; + try { + BlackBerryContactList agenda = (BlackBerryContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_ONLY); + StringBuffer contacts = new StringBuffer("["); + if (agenda != null) { + Enumeration matches; + String name = options.get("nameFilter")!=null?options.get("nameFilter").toString():""; + if (name != "") { + matches = agenda.itemsByName(name); + callbackHook = "search_"; + } else { + matches = agenda.items(); + callbackHook = "global_"; } - return contacts.deleteCharAt(contacts.length() - 1).append("]").toString(); + int pageSize = 0, pageNumber = 0; + if (options.contains("pageSize")) pageSize = Integer.parseInt(options.get("pageSize").toString()); + if (options.contains("pageNumber")) pageNumber = Integer.parseInt(options.get("pageNumber").toString()); + if (pageSize > 0) { + for (int i = 0; i < pageSize*pageNumber && matches.hasMoreElements(); i++) { + matches.nextElement(); + } + for (int j = 0; j < pageSize && matches.hasMoreElements(); j++) { + BlackBerryContact contact = (BlackBerryContact)matches.nextElement(); + ContactsCommand.addContactToBuffer(contacts, contact); + contacts.append(','); + } + } else { + while (matches.hasMoreElements()) { + BlackBerryContact contact = (BlackBerryContact)matches.nextElement(); + ContactsCommand.addContactToBuffer(contacts, contact); + contacts.append(','); + } + } + if (contacts.length() > 1) contacts = contacts.deleteCharAt(contacts.length() - 1); + contacts.append("];"); + // Return an assignment to the contact manager contacts array with the contacts JSON generated above. + // Also call the right onSuccess if it exists. + return ";" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".contacts=" + contacts.toString() + "if (" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess) { " + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess();" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess = null;" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError = null; };"; + } else { + // TODO: If cannot get reference to Agenda, should the error or success callback be called? + return ";" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + ".contacts=" + contacts.append("];").toString() + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess = null;" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError = null;"; } } catch (Exception ex) { System.out.println("Exception getting contact list: " + ex.getMessage()); + return ";if (" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError) { " + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError();" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onSuccess = null;" + ContactsCommand.CONTACT_MANAGER_JS_NAMESPACE + "." + callbackHook + "onError = null; };"; } - return null; } - + private static void addContactToBuffer(StringBuffer buff, BlackBerryContact contact) { + // TODO: Eventually extend this to return proper labels/values for differing phone/email types. + buff.append("{email:[{'label':'mobile','value':'"); + buff.append(contact.getString(Contact.EMAIL, 0)); + buff.append("'}], phoneNumber:[{'label':'mobile','value':'"); + buff.append(contact.getString(Contact.TEL, 0)); + buff.append("'}], firstName:'"); + // See if there is a meaningful name set for the contact. + if (contact.countValues(Contact.NAME) > 0) { + final String[] name = contact.getStringArray(Contact.NAME, 0); + final String firstName = name[Contact.NAME_GIVEN]; + final String lastName = name[Contact.NAME_FAMILY]; + if (firstName != null) buff.append(firstName + "',lastName:'"); + else buff.append("',lastName:'"); + if (lastName != null) buff.append(lastName + "',"); + else buff.append("',"); + } else { + buff.append("',lastName:''"); + } + buff.append(",address:'"); + // Build up a meaningful address field. + if (contact.countValues(Contact.ADDR) > 0) { + String address = ""; + final String[] addr = contact.getStringArray(Contact.ADDR, 0); + final String street = addr[Contact.ADDR_STREET]; + final String city = addr[Contact.ADDR_LOCALITY]; + final String state = addr[Contact.ADDR_REGION]; + final String country = addr[Contact.ADDR_COUNTRY]; + final String postalCode = addr[Contact.ADDR_POSTALCODE]; + if (street!=null) address = street.replace('\'',' '); + if (city!=null) if (address.length() > 0) address += ", " + city.replace('\'',' '); else address = city.replace('\'',' '); + if (state!=null) if (address.length() > 0) address += ", " + state.replace('\'',' '); else address = state.replace('\'',' '); + if (country!=null) if (address.length() > 0) address += ", " + country.replace('\'',' '); else address = country.replace('\'',' '); + if (postalCode!=null) if (address.length() > 0) address += ", " + postalCode.replace('\'',' '); else address = postalCode.replace('\'',' '); + buff.append(address); + } + buff.append("'}"); + } } diff --git a/blackberry/src/com/nitobi/phonegap/api/impl/DeviceCommand.java b/blackberry/src/com/nitobi/phonegap/api/impl/DeviceCommand.java new file mode 100644 index 00000000..0117a6ee --- /dev/null +++ b/blackberry/src/com/nitobi/phonegap/api/impl/DeviceCommand.java @@ -0,0 +1,74 @@ +/** + * 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.nitobi.phonegap.api.impl; + +import com.nitobi.phonegap.api.Command; +import net.rim.device.api.system.DeviceInfo; + +/** + * Configures the Device API. + * + * @author Jose Noheda [jose.noheda@gmail.com] + * + */ +public class DeviceCommand implements Command { + + private static final String CODE = "PhoneGap=initialize"; + + /** + * Determines whether the specified instruction is accepted by the command. + * @param instruction The string instruction passed from JavaScript via cookie. + * @return true if the Command accepts the instruction, false otherwise. + */ + public boolean accept(String instruction) { + return instruction != null && instruction.startsWith(CODE); + } + + /** + * Fills the JS variable 'device' with: + * Model + * Flash memory available + * Platform + * Vendor + * Battery + * Software version + * Camera support + * ID + * Simulator + * + */ + public String execute(String instruction) { + StringBuffer deviceInfo = new StringBuffer(";device.name = '"); + deviceInfo.append(DeviceInfo.getDeviceName()).append("';device.flash = "); + deviceInfo.append(DeviceInfo.getTotalFlashSize()).append(";device.platform = '"); + deviceInfo.append(DeviceInfo.getPlatformVersion().length()>0?DeviceInfo.getPlatformVersion():"Emulator").append("';device.vendor = '"); + deviceInfo.append(DeviceInfo.getManufacturerName()).append("';device.battery = "); + deviceInfo.append(DeviceInfo.getBatteryLevel()).append(";device.version = '"); + deviceInfo.append(DeviceInfo.getSoftwareVersion()).append("';device.isSimulator = "); + deviceInfo.append(DeviceInfo.isSimulator()).append(";device.hasCamera = "); + deviceInfo.append(DeviceInfo.hasCamera()).append(";device.uuid = "); + deviceInfo.append(DeviceInfo.getDeviceId()).append(";"); + return deviceInfo.toString(); + } + +} diff --git a/blackberry/src/com/nitobi/phonegap/api/impl/GeoLocationCommand.java b/blackberry/src/com/nitobi/phonegap/api/impl/GeoLocationCommand.java index 2993736f..f2cc3abd 100644 --- a/blackberry/src/com/nitobi/phonegap/api/impl/GeoLocationCommand.java +++ b/blackberry/src/com/nitobi/phonegap/api/impl/GeoLocationCommand.java @@ -46,7 +46,7 @@ public class GeoLocationCommand implements Command { private static final int START_COMMAND = 2; private static final int CHECK_COMMAND = 3; private static final int CAPTURE_INTERVAL = 5; - private static final String CODE = "gap://location"; + private static final String CODE = "PhoneGap=location"; private Position position; private boolean availableGPS = true; @@ -59,10 +59,10 @@ public GeoLocationCommand() { availableGPS = false; } } - /** - * Able to run the location command (all options). - * Ex: gap://location/start + * Determines whether the specified instruction is accepted by the command. + * @param instruction The string instruction passed from JavaScript via cookie. + * @return true if the Command accepts the instruction, false otherwise. */ public boolean accept(String instruction) { return instruction != null && instruction.startsWith(CODE); @@ -77,7 +77,7 @@ public void clearPosition() { /** * Executes the following sub-commands: - * START: Initiliazes the internal GPS module + * START: Initializes the internal GPS module * STOP: Stops GPS module (saving battery life) * CHECK: Reads latest position available * MAP: Invokes the internal MAP application @@ -96,7 +96,11 @@ public String execute(String instruction) { } return null; } - + /** + * Parses the specified instruction and the parameters and determines what type of functional call is being made. + * @param instruction The string instruction passed from JavaScript via cookie. + * @return Integer representing action type. + */ private int getCommand(String instruction) { String command = instruction.substring(instruction.lastIndexOf('/') + 1); if ("map".equals(command)) return MAP_COMMAND; @@ -145,7 +149,18 @@ public void locationUpdated(LocationProvider provider, Location location) { } else command.clearPosition(); } - public void providerStateChanged(LocationProvider provider, int newState) {} + public void providerStateChanged(LocationProvider provider, int newState) { + switch (newState) { + case LocationProvider.AVAILABLE: + break; + case LocationProvider.OUT_OF_SERVICE: + // TODO: Should call fail() here. + break; + case LocationProvider.TEMPORARILY_UNAVAILABLE: + break; + } + + } } } diff --git a/blackberry/src/com/nitobi/phonegap/api/impl/MediaCommand.java b/blackberry/src/com/nitobi/phonegap/api/impl/MediaCommand.java new file mode 100644 index 00000000..d0742027 --- /dev/null +++ b/blackberry/src/com/nitobi/phonegap/api/impl/MediaCommand.java @@ -0,0 +1,94 @@ +/** + * The MIT License + * ------------------------------------------------------------- + * Copyright (c) 2008, Rob Ellis, Brock Whitten, Brian Leroux, Joe Bowser, Dave Johnson, Fil Maj, 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.nitobi.phonegap.api.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; + +import javax.microedition.media.Player; +import com.nitobi.phonegap.api.Command; + +/** + * Wraps playing local storage (music) media on the BlackBerry for access from JavaScript. + * @author Fil Maj + * + */ +public class MediaCommand implements Command { + private static final String CODE = "PhoneGap=media"; + private Player musicPlayer; + private Hashtable extToMime = new Hashtable(); + + public MediaCommand() { + extToMime.put("wav", "audio/x-wav"); + extToMime.put("au", "audio/basic"); + extToMime.put("snd", "audio/basic"); + extToMime.put("mid", "audio/mid"); + extToMime.put("rmi", "audio/mid"); + extToMime.put("mp3", "audio/mpeg"); + extToMime.put("aif", "audio/x-aiff"); + extToMime.put("aiff", "audio/x-aiff"); + extToMime.put("aifc", "audio/x-aiff"); + extToMime.put("ra", "audio/x-pn-realaudio"); + extToMime.put("ram", "audio/x-pn-realaudio"); + } + + /** + * Determines whether the specified instruction is accepted by the command. + * @param instruction The string instruction passed from JavaScript via cookie. + * @return true if the Command accepts the instruction, false otherwise. + */ + public boolean accept(String instruction) { + return instruction != null && instruction.startsWith(CODE); + } + + public String execute(String instruction) { + String mediaURI = instruction.substring(instruction.indexOf('/')+1); + try + { + InputStream in = getClass().getResourceAsStream("/" + mediaURI); + String audioMime = this.mapExtensionToMime(mediaURI.substring(mediaURI.lastIndexOf('.')+1)); + if (audioMime == null) return ";alert('[PhoneGap Error] Media type not supported.');"; + // Create a media player with our inputstream + musicPlayer = javax.microedition.media.Manager.createPlayer(in, audioMime); + musicPlayer.realize(); + musicPlayer.prefetch(); + musicPlayer.setLoopCount(1); + musicPlayer.start(); + return ""; + } + catch (IOException e) { + return ";alert('[PhoneGap Error] Media file I/O exception.');"; + } catch (javax.microedition.media.MediaException e) { + return ";alert('[PhoneGap Error] Could not play and/or determine media file type.');"; + } + } + /** + * Maps media file extensions to mime type, for use with the sound player. + * @param extension The media file extension + * @return A mime type as a string + */ + private String mapExtensionToMime(String extension) { + return extToMime.get(extension).toString(); + } +} diff --git a/blackberry/src/com/nitobi/phonegap/api/impl/NotificationCommand.java b/blackberry/src/com/nitobi/phonegap/api/impl/NotificationCommand.java new file mode 100644 index 00000000..867a22b9 --- /dev/null +++ b/blackberry/src/com/nitobi/phonegap/api/impl/NotificationCommand.java @@ -0,0 +1,106 @@ +/** + * 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.nitobi.phonegap.api.impl; + +import net.rim.device.api.system.Alert; + +import com.nitobi.phonegap.api.Command; + +/** + * Vibrates the phone if able. + * + * @author Jose Noheda + * + */ +public class NotificationCommand implements Command { + + private static final int VIBRATE_COMMAND = 0; + private static final int BEEP_COMMAND = 1; + private static final int DURATION = 5; + private static final String CODE = "PhoneGap=notification"; + + private static final short A = 440; //440.00 + private static final short NOTE_DURATION = 500; + private static final short PAUSE_DURATION = 50; + private static final int TUNE_LENGTH = 4; + private static final short[] TUNE = new short[] + { + A, NOTE_DURATION, 0, PAUSE_DURATION + }; + /** + * Determines whether the specified instruction is accepted by the command. + * @param instruction The string instruction passed from JavaScript via cookie. + * @return true if the Command accepts the instruction, false otherwise. + */ + public boolean accept(String instruction) { + return instruction != null && instruction.startsWith(CODE); + } + + public String execute(String instruction) { + switch (getCommand(instruction)) { + case VIBRATE_COMMAND: + if (Alert.isVibrateSupported()) Alert.startVibrate(getVibrateDuration(instruction)); + break; + case BEEP_COMMAND: + if (Alert.isAudioSupported()) Alert.startAudio(getTune(instruction), 99); + break; + } + return null; + } + private int getCommand(String instruction) { + String command = instruction.substring(CODE.length()+1); + if (command.startsWith("beep")) return BEEP_COMMAND; + if (command.startsWith("vibrate")) return VIBRATE_COMMAND; + return -1; + } + /** + * Parses the vibrate instruction and tries to extract the specified duration. Returns the default duration if there are issues parsing. + * @param instruction The instruction called from the JS. + * @return The number of seconds the vibration should last. + */ + private int getVibrateDuration(String instruction) { + try { + return Integer.parseInt(instruction.substring(instruction.lastIndexOf('/') + 1)); + } catch(Exception ex) { + return DURATION; + } + } + private short[] getTune(String instruction) { + String beepParam = instruction.substring(CODE.length()+1); + int param = 1; + try { + param = Integer.parseInt(beepParam.substring(beepParam.indexOf('/')+1)); + } catch(Exception e) { + param = 1; + } + short[] theTune = new short[TUNE_LENGTH * param]; + if (param == 1) + return TUNE; + else { + for (int i = 0; i < param; i++) { + System.arraycopy(TUNE, 0, theTune, i*TUNE_LENGTH, TUNE_LENGTH); + } + } + return theTune; + } +} \ No newline at end of file diff --git a/blackberry/src/com/nitobi/phonegap/api/impl/TelephonyCommand.java b/blackberry/src/com/nitobi/phonegap/api/impl/TelephonyCommand.java index 958f4938..4ff40097 100644 --- a/blackberry/src/com/nitobi/phonegap/api/impl/TelephonyCommand.java +++ b/blackberry/src/com/nitobi/phonegap/api/impl/TelephonyCommand.java @@ -35,11 +35,8 @@ */ public class TelephonyCommand implements Command { - private static final String CODE = "gap://call"; + private static final String CODE = "PhoneGap=call"; - /** - * Able to run the call command. Ex: gap://call/555666777 - */ public boolean accept(String instruction) { return instruction != null && instruction.startsWith(CODE); } diff --git a/blackberry/src/com/nitobi/phonegap/io/AsynchronousResourceFetcher.java b/blackberry/src/com/nitobi/phonegap/io/AsynchronousResourceFetcher.java index 5dd880c2..51666a4d 100644 --- a/blackberry/src/com/nitobi/phonegap/io/AsynchronousResourceFetcher.java +++ b/blackberry/src/com/nitobi/phonegap/io/AsynchronousResourceFetcher.java @@ -32,16 +32,14 @@ public class AsynchronousResourceFetcher implements Runnable { private String url; private Callback callback; - private ConnectionManager connectionManager; - public AsynchronousResourceFetcher(String url, Callback callback, ConnectionManager connectionManager) { + public AsynchronousResourceFetcher(String url, Callback callback) { this.url = url; this.callback = callback; - this.connectionManager = connectionManager; } public void run() { - callback.execute(connectionManager.getUnmanagedConnection(url)); + callback.execute(ConnectionManager.getUnmanagedConnection(url, null, null)); } } diff --git a/blackberry/src/com/nitobi/phonegap/io/ConnectionManager.java b/blackberry/src/com/nitobi/phonegap/io/ConnectionManager.java index f2ce1b42..08d5a3ad 100644 --- a/blackberry/src/com/nitobi/phonegap/io/ConnectionManager.java +++ b/blackberry/src/com/nitobi/phonegap/io/ConnectionManager.java @@ -27,6 +27,8 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; import javax.microedition.io.Connection; import javax.microedition.io.Connector; @@ -34,7 +36,10 @@ import javax.microedition.io.InputConnection; import net.rim.device.api.io.Base64OutputStream; +import net.rim.device.api.io.http.HttpHeaders; +import net.rim.device.api.io.http.HttpProtocolConstants; import net.rim.device.api.system.Application; +import net.rim.device.api.util.StringUtilities; /** * Manages all HTTP connections. @@ -46,19 +51,88 @@ public final class ConnectionManager { public static final String DATA = "data"; public static final String DATA_PROTOCOL = DATA + ":///"; - private static final byte[] DATA_URL = (ConnectionManager.DATA + ":text/html;base64,").getBytes(); + private static final String URI_SUFFIX = ";charset=utf-8;base64,"; + private static final byte[] DATA_URL_HTML = (ConnectionManager.DATA + ":text/html" + URI_SUFFIX).getBytes(); + private static final byte[] DATA_URL_JS = (ConnectionManager.DATA + ":text/javascript" + URI_SUFFIX).getBytes(); + private static final byte[] DATA_URL_IMG_JPG = (ConnectionManager.DATA + ":image/jpeg" + URI_SUFFIX).getBytes(); + private static final byte[] DATA_URL_CSS = (ConnectionManager.DATA + ":text/css" + URI_SUFFIX).getBytes(); + private static final byte[] DATA_URL_PLAIN = (ConnectionManager.DATA + ":text/plain" + URI_SUFFIX).getBytes(); /** * Creates a connection and returns it. Calling this method without care may saturate BB capacity. * * @param url a http:// or data:// URL */ - public HttpConnection getUnmanagedConnection(String url) { - if ((url != null) && (url.trim().length() > 0)) - return isInternal(url) ? getDataProtocolConnection(url) : getExternalConnection(url); - return null; + public static HttpConnection getUnmanagedConnection(String url, HttpHeaders requestHeaders, byte[] postData) { + HttpConnection conn = null; + OutputStream out = null; + if ((url != null) && (url.trim().length() > 0)) { + conn = isInternal(url) ? getDataProtocolConnection(url) : getExternalConnection(url); + } else { + return conn; + } + try { + //conn = setConnectionRequestHeaders(url, requestHeaders, conn); + if (postData == null) { + conn.setRequestMethod(HttpConnection.GET); + } else { + conn.setRequestMethod(HttpConnection.POST); + conn.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_LENGTH, String.valueOf(postData.length)); + out = conn.openOutputStream(); + out.write(postData); + } + } catch (IOException e1) { + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e2) { + } + } + } + return conn; } + public static HttpConnection setConnectionRequestHeaders(String url, HttpHeaders requestHeaders, HttpConnection conn) { + HttpConnection returnConn = conn; + if (requestHeaders != null) { + // From + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3 + // + // Clients SHOULD NOT include a Referer header field in a + // (non-secure) HTTP + // request if the referring page was transferred with a secure + // protocol. + String referer = requestHeaders.getPropertyValue("referer"); + boolean sendReferrer = true; + + if (referer != null && StringUtilities.startsWithIgnoreCase(referer,"https:") && !StringUtilities.startsWithIgnoreCase(url, "https:")) { + sendReferrer = false; + } + int size = requestHeaders.size(); + for (int i = 0; i < size;) { + String header = requestHeaders.getPropertyKey(i); + + // Remove referer header if needed. + if (!sendReferrer && header.equals("referer")) { + requestHeaders.removeProperty(i); + --size; + continue; + } + + String value = requestHeaders.getPropertyValue(i++); + if (value != null) { + try { + returnConn.setRequestProperty(header, value); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } + return returnConn; + } /** * Loads an external URL and provides a connection that holds the array of bytes. Internal * URLs (data://) simply pass through. @@ -66,7 +140,7 @@ public HttpConnection getUnmanagedConnection(String url) { * @param url a http:// or data:// URL */ public InputConnection getPreLoadedConnection(String url) { - InputConnection connection = getUnmanagedConnection(url); + InputConnection connection = getUnmanagedConnection(url, null, null); if ((connection != null) && (!isInternal(url))) { try { final byte[] data = read(connection.openInputStream()); @@ -97,7 +171,7 @@ public void close() throws IOException { /** * Detects data:// URLs */ - public boolean isInternal(String url) { + public static boolean isInternal(String url) { return (url != null) && url.startsWith(ConnectionManager.DATA_PROTOCOL); } @@ -117,24 +191,39 @@ private static void close(Connection connection) { private static HttpConnection getExternalConnection(String url) { try { - return (HttpConnection) Connector.open(url); + HttpConnection con = (HttpConnection)Connector.open(url); + return con; } catch (Exception ex) { return null; } } private static HttpConnection getDataProtocolConnection(String url) { - String dataUrl = url.startsWith(ConnectionManager.DATA_PROTOCOL) ? url.substring(ConnectionManager.DATA_PROTOCOL.length()-1) : url; + String dataUrl = url.startsWith(ConnectionManager.DATA_PROTOCOL) ? url.substring(ConnectionManager.DATA_PROTOCOL.length() - 1) : url; ByteArrayOutputStream output = new ByteArrayOutputStream(); try { - output.write(ConnectionManager.DATA_URL); + if (dataUrl.endsWith(".html") || dataUrl.endsWith(".htm")) { + output.write(ConnectionManager.DATA_URL_HTML); + } else if (dataUrl.endsWith(".js")) { + output.write(ConnectionManager.DATA_URL_JS); + } else if (dataUrl.endsWith(".jpg") || dataUrl.endsWith(".jpeg")) { + output.write(ConnectionManager.DATA_URL_IMG_JPG); + } else if (dataUrl.endsWith(".css")) { + output.write(ConnectionManager.DATA_URL_CSS); + } else { + output.write(ConnectionManager.DATA_URL_PLAIN); + } Base64OutputStream boutput = new Base64OutputStream(output); - boutput.write(read(Application.class.getResourceAsStream(dataUrl))); + InputStream theResource = Application.class.getResourceAsStream(dataUrl); + byte[] resourceBytes = read(theResource); + boutput.write(resourceBytes); boutput.flush(); boutput.close(); output.flush(); output.close(); - return (HttpConnection) Connector.open(output.toString()); + Connection outputCon = Connector.open(output.toString()); + HttpConnection outputHttp = (HttpConnection) outputCon; + return outputHttp; } catch (IOException ex) { return null; } @@ -153,5 +242,4 @@ private static byte[] read(InputStream input) throws IOException { } return bytes.toByteArray(); } - } diff --git a/blackberry/src/com/nitobi/phonegap/io/PrimaryResourceFetchThread.java b/blackberry/src/com/nitobi/phonegap/io/PrimaryResourceFetchThread.java new file mode 100644 index 00000000..71710203 --- /dev/null +++ b/blackberry/src/com/nitobi/phonegap/io/PrimaryResourceFetchThread.java @@ -0,0 +1,38 @@ +package com.nitobi.phonegap.io; + +import javax.microedition.io.HttpConnection; + +import net.rim.device.api.browser.field.Event; +import net.rim.device.api.io.http.HttpHeaders; + +import com.nitobi.phonegap.PhoneGap; + +public class PrimaryResourceFetchThread extends Thread +{ + private PhoneGap _application; + private Event _event; + private byte[] _postData; + private HttpHeaders _requestHeaders; + private String _url; + + public PrimaryResourceFetchThread(String url, HttpHeaders requestHeaders, byte[] postData, + Event event, PhoneGap application) + { + _url = url; + _requestHeaders = requestHeaders; + _postData = postData; + _application = application; + _event = event; + } + + public void run() + { + HttpConnection connection = null; + if (_url.startsWith(PhoneGap.PHONEGAP_PROTOCOL)) { + connection = _application._currentConnection; + } else { + connection = ConnectionManager.getUnmanagedConnection(_url, _requestHeaders, _postData); + } + _application.processConnection(connection, _event); + } +} diff --git a/blackberry/src/com/nitobi/phonegap/io/SecondaryResourceFetchThread.java b/blackberry/src/com/nitobi/phonegap/io/SecondaryResourceFetchThread.java new file mode 100644 index 00000000..b5e5f232 --- /dev/null +++ b/blackberry/src/com/nitobi/phonegap/io/SecondaryResourceFetchThread.java @@ -0,0 +1,169 @@ +/* + * SecondaryResourceFetchThread.java + * + * Copyright © 1998-2008 Research In Motion Ltd. + * + * Note: For the sake of simplicity, this sample application may not leverage + * resource bundles and resource strings. However, it is STRONGLY recommended + * that application developers make use of the localization features available + * within the BlackBerry development platform to ensure a seamless application + * experience across a variety of languages and geographies. For more information + * on localizing your application, please refer to the BlackBerry Java Development + * Environment Development Guide associated with this release. + */ + +package com.nitobi.phonegap.io; + +import java.util.Vector; + +import javax.microedition.io.HttpConnection; + +import net.rim.device.api.browser.field.BrowserContent; +import net.rim.device.api.browser.field.RequestedResource; + + +public class SecondaryResourceFetchThread extends Thread +{ + + /** + * Callback browser field. + */ + private BrowserContent _browserField; + + /** + * Images to retrieve. + */ + private Vector _imageQueue; + + /** + * True is all images have been enqueued. + */ + private boolean _done; + + /** + * Sync object. + */ + private static Object _syncObject = new Object(); + + /** + * Secondary thread. + */ + private static SecondaryResourceFetchThread _currentThread; + + + /** + * Enqueues secondary resource for a browser field. + * + * @param resource - resource to retrieve. + * @param referrer - call back browsr field. + */ + public static void enqueue(RequestedResource resource, BrowserContent referrer) + { + if (resource == null) + { + return; + } + + synchronized( _syncObject ) + { + + // Create new thread. + if (_currentThread == null) + { + _currentThread = new SecondaryResourceFetchThread(); + _currentThread.start(); + } + else + { + // If thread alread is running, check that we are adding images for the same browser field. + if (referrer != _currentThread._browserField) + { + synchronized( _currentThread._imageQueue) + { + // If the request is for a different browser field, + // clear old elements. + _currentThread._imageQueue.removeAllElements(); + } + } + } + + synchronized( _currentThread._imageQueue) + { + _currentThread._imageQueue.addElement(resource); + } + + _currentThread._browserField = referrer; + } + } + + /** + * Constructor + * + */ + private SecondaryResourceFetchThread() + { + _imageQueue = new Vector(); + } + + /** + * Indicate that all images have been enqueued for this browser field. + */ + static void doneAddingImages() + { + synchronized( _syncObject ) + { + if (_currentThread != null) + { + _currentThread._done = true; + } + } + } + + public void run() + { + while (true) + { + if (_done) + { + // Check if we are done requesting images. + synchronized( _syncObject ) + { + synchronized( _imageQueue ) + { + if (_imageQueue.size() == 0) + { + _currentThread = null; + break; + } + } + } + } + + RequestedResource resource = null; + + // Request next image. + synchronized( _imageQueue ) + { + if (_imageQueue.size() > 0) + { + resource = (RequestedResource)_imageQueue.elementAt(0); + _imageQueue.removeElementAt(0); + } + } + + if (resource != null) + { + + HttpConnection connection = ConnectionManager.getUnmanagedConnection(resource.getUrl(), resource.getRequestHeaders(), null); + resource.setHttpConnection(connection); + + // Signal to the browser field that resource is ready. + if (_browserField != null) + { + _browserField.resourceReady(resource); + } + } + } + } + +} diff --git a/blackberry/src/com/nitobi/phonegap/model/Position.java b/blackberry/src/com/nitobi/phonegap/model/Position.java index 652883b6..588a95e2 100644 --- a/blackberry/src/com/nitobi/phonegap/model/Position.java +++ b/blackberry/src/com/nitobi/phonegap/model/Position.java @@ -77,7 +77,7 @@ public void setVelocity(float velocity) { } public String toJavascript() { - return "new Position(" + _lat + "," + _lng + ",1," + altitude + ",1" + heading + "," + velocity + ")"; + return "new Position(new Coordinates(" + _lat + "," + _lng + "," + altitude + ",1," + heading + "," + velocity + "))"; } } diff --git a/blackberry/src/www/css/test.css b/blackberry/src/www/css/test.css new file mode 100644 index 00000000..b851b995 --- /dev/null +++ b/blackberry/src/www/css/test.css @@ -0,0 +1 @@ +a { color:red; } \ No newline at end of file diff --git a/blackberry/src/www/js/camera.js b/blackberry/src/www/js/camera.js index 0ef78214..6eef7181 100644 --- a/blackberry/src/www/js/camera.js +++ b/blackberry/src/www/js/camera.js @@ -7,8 +7,8 @@ function Camera() { } Camera.prototype.launch = function () { - if (Device.hasCamera) Device.exec("camera", ["obtain"], true); - else alert("Camera not supported"); + if (device.hasCamera) device.exec("camera", ["obtain"], true); + else alert("Camera not supported on this device."); } /** @@ -18,8 +18,13 @@ Camera.prototype.launch = function () { * @param {Object} options */ Camera.prototype.getPicture = function(successCallback, errorCallback, options) { - if (Device.hasCamera) Device.exec("camera", ["picture"], true); - else alert("Camera not supported"); + if (device.hasCamera) { + if (successCallback) this.onSuccess = successCallback; + else this.onSuccess = null; + if (errorCallback) this.onError = errorCallback; + else this.onError = null; + device.exec("camera", ["picture"], true); + } else alert("Camera not supported"); } if (typeof navigator.camera == "undefined") navigator.camera = new Camera(); \ No newline at end of file diff --git a/blackberry/src/www/js/contacts.js b/blackberry/src/www/js/contacts.js index d20e207b..75a59f8e 100644 --- a/blackberry/src/www/js/contacts.js +++ b/blackberry/src/www/js/contacts.js @@ -3,29 +3,94 @@ * @constructor */ function Contact() { - this.name = ""; - this.phone = ""; + this.firstName = ""; + this.lastName = ""; + this.phoneNumber = {}; this.address = ""; + this.email = {}; } -/** - * - * @param {Object} successCallback - * @param {Object} errorCallback - * @param {Object} options - */ -Contact.prototype.get = function(successCallback, errorCallback, options) { - -} - - function ContactManager() { this.contacts = []; - this.timestap = new Date().getTime(); + this.timestamp = new Date().getTime(); + // Options used when calling ContactManager functions. + this.options = { + 'pageSize':0, + 'pageNumber':0, + 'nameFilter':'', + 'contactID':0 + }; } - -ContactManager.prototype.get = function(successCallback, errorCallback, options) { - Device.exec("contacts", [options.operation, options.field, options.value], true); +ContactManager.prototype.formParams = function(options, startArray) { + var params = []; + if (startArray) params = startArray; + if (options.pageSize && options.pageSize > 0) params.push("pageSize:" + options.pageSize); + if (options.pageNumber) params.push("pageNumber:" + options.pageNumber); + if (options.nameFilter) params.push("nameFilter:" + options.nameFilter); + if (options.contactID) params.push("contactID:" + options.contactID); + return params; +} +ContactManager.prototype.newContact = function(contact, successCallback, errorCallback, options) { + if (!contact) { + alert("[PhoneGap Error] newContact function not provided with a contact parameter."); + return; + } else { + if (!contact.firstName || !contact.lastName || !contact.phoneNumber || !contact.address || !contact.email) { + alert("[PhoneGap Error] newContact function parameter 'contact' does not have proper contact members (firstName, lastName, phoneNumber, address and email)."); + return; + } + options.push("firstName:" + contact.firstName); + options.push("lastName:" + contact.lastName); + options.push("address:" + contact.address); + // Create a phone number parameter that we can parse on the BlackBerry end. + var phones = ''; + for (var i = 0; i < contact.phoneNumber.length; i++) { + phones += contact.phoneNumber[i].label + '='; + phones += contact.phoneNumber[i].value + '|'; + } + options.push("phoneNumber:" + phones.substring(0,phones.length-1); + var emails = ''; + for (var i = 0; i < contact.email.length; i++) { + emails += contact.email[i].label + '='; + emails += contact.email[i].value + '|'; + } + options.push("email:" + emails.substring(0,emails.length-1); + this.new_onSuccess = successCallback; + this.new_onError = errorCallback; + device.exec("new", options, true); + } +} +ContactManager.prototype.displayContact = function(successCallback, errorCallback, options) { + if (options.nameFilter && options.nameFilter.length > 0) { + var params = ["search"]; + params = this.formParams(options,params); + this.search_onSuccess = successCallback; + this.search_onError = errorCallback; + device.exec("contacts", params, true); + } else { + ContactManager.getAllContacts(successCallback,errorCallback,options); + return; + } +} +ContactManager.prototype.getAllContacts = function(successCallback, errorCallback, options) { + this.global_onSuccess = successCallback; + this.global_onError = errorCallback; + var params = ["getall"]; + params = this.formParams(options,params); + device.exec("contacts", params, true); +} +ContactManager.prototype.chooseContact = function(successCallback, options) { + this.choose_onSuccess = successCallback; + var params = ["choose"]; + params = this.formParams(options,params); + device.exec("contacts", params, true); +} +ContactManager.prototype.removeContact = function(successCallback, errorCallback, options) { + this.remove_onSuccess = successCallback; + this.remove_onError = errorCallback; + var params = ["remove"]; + params = this.formParams(options,params); + device.exec("contacts", params, true); } if (typeof navigator.ContactManager == "undefined") navigator.ContactManager = new ContactManager(); \ No newline at end of file diff --git a/blackberry/src/www/js/device.js b/blackberry/src/www/js/device.js index dc8473d4..dc25835d 100644 --- a/blackberry/src/www/js/device.js +++ b/blackberry/src/www/js/device.js @@ -1,4 +1,4 @@ -window.Device = { +window.device = { isIPhone: false, isIPod: false, isBlackBerry: true, @@ -6,28 +6,26 @@ window.Device = { init: function() { this.exec("initialize"); this.poll(function() { - Device.available = typeof Device.model == "string"; + device.available = typeof device.name == "string"; }); }, exec: function(command, params, sync) { - if (Device.available || command == "initialize") { + if (device.available || command == "initialize") { try { - var url = "gap://" + command; - if (params) url += "/" + params.join("/"); - document.location = url; + var cookieCommand = "PhoneGap=" + command; + if (params) cookieCommand += "/" + params.join("/"); + document.cookie = cookieCommand; if (sync) this.poll(); } catch(e) { console.log("Command '" + command + "' has not been executed, because of exception: " + e); alert("Error executing command '" + command + "'.") } + } else { + alert("Device not available YET - still loading."); } }, poll: function(callback) { eval(document.cookie + (callback ? ";callback();" : "")); - }, - vibrate: function(secs) { - return Device.exec("vibrate", [secs]); } }; - -window.Device.init(); \ No newline at end of file +window.device.init(); \ No newline at end of file diff --git a/blackberry/src/www/js/geolocation.js b/blackberry/src/www/js/geolocation.js index 8f230e5e..99ba3b06 100644 --- a/blackberry/src/www/js/geolocation.js +++ b/blackberry/src/www/js/geolocation.js @@ -22,7 +22,7 @@ Geolocation.prototype.start = function() { alert("GPS already started"); return; } - Device.exec("location", ["start"], true); + device.exec("location", ["start"], true); } /** @@ -34,7 +34,7 @@ Geolocation.prototype.stop = function() { return; } if (this.locationTimeout) window.clearTimeout(this.locationTimeout); - Device.exec("location", ["stop"], true); + device.exec("location", ["stop"], true); } /** @@ -45,11 +45,11 @@ Geolocation.prototype.map = function() { alert("No position to map yet"); return; } - Device.exec("location", ["map"], true); + device.exec("location", ["map"], true); } /** - * Asynchronously adquires the current position. + * Asynchronously acquires the current position. * * @param {Function} successCallback The function to call when the position * data is available @@ -62,8 +62,7 @@ Geolocation.prototype.map = function() { */ Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) { if (!this.started) { - alert("GPS not started"); - return; + this.start(); } this.onSuccess = successCallback; this.locationTimeout = window.setInterval("navigator.geolocation._getCurrentPosition();", 1000); @@ -71,10 +70,10 @@ Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallba Geolocation.prototype._getCurrentPosition = function() { this.lastPosition = null; - Device.exec("location", ["check"], true); + device.exec("location", ["check"], true); if (this.lastPosition != null) { window.clearTimeout(this.locationTimeout); - if (this.onSuccess) this.onSuccess(); + if (this.onSuccess) this.onSuccess(this.lastPosition); this.onSuccess = null; } } diff --git a/blackberry/src/www/js/media.js b/blackberry/src/www/js/media.js new file mode 100644 index 00000000..58dc7752 --- /dev/null +++ b/blackberry/src/www/js/media.js @@ -0,0 +1,5 @@ +navigator.media = { + playSound: function(media) { + window.device.exec("media",[media],true); + } +}; \ No newline at end of file diff --git a/blackberry/src/www/js/notification.js b/blackberry/src/www/js/notification.js new file mode 100644 index 00000000..f4092d9b --- /dev/null +++ b/blackberry/src/www/js/notification.js @@ -0,0 +1,8 @@ +navigator.notification = { + vibrate: function(secs) { + window.device.exec("notification/vibrate",[secs]); + }, + beep: function(times) { + window.device.exec("notification/beep",[times]); + } +}; \ No newline at end of file diff --git a/blackberry/src/www/js/position.js b/blackberry/src/www/js/position.js index 21439dd8..685beeb7 100644 --- a/blackberry/src/www/js/position.js +++ b/blackberry/src/www/js/position.js @@ -9,7 +9,12 @@ * @param {Object} vel * @constructor */ -function Position(lat, lng, acc, alt, altacc, head, vel) { +function Position(coords) { + this.coords = coords; + this.timestamp = new Date().getTime(); +} + +function Coordinates(lat, lng, alt, acc, head, vel) { /** * The latitude of the position. */ @@ -26,10 +31,6 @@ function Position(lat, lng, acc, alt, altacc, head, vel) { * The altitude of the position. */ this.altitude = alt; - /** - * The altitude accuracy of the position. - */ - this.altitudeAccuracy = altacc; /** * The direction the device is moving at the position. */ @@ -37,13 +38,9 @@ function Position(lat, lng, acc, alt, altacc, head, vel) { /** * The velocity with which the device is moving at the position. */ - this.velocity = vel; - /** - * The time that the position was obtained. - */ - this.timestamp = new Date().getTime(); + this.speed = vel; } - + /** * This class specifies the options for requesting position data. * @constructor @@ -59,7 +56,7 @@ function PositionOptions() { */ this.timeout = 10000; } - + /** * This class contains information about any GSP errors. * @constructor @@ -68,8 +65,8 @@ function PositionError() { this.code = null; this.message = ""; } - + PositionError.UNKNOWN_ERROR = 0; PositionError.PERMISSION_DENIED = 1; PositionError.POSITION_UNAVAILABLE = 2; -PositionError.TIMEOUT = 3; +PositionError.TIMEOUT = 3; \ No newline at end of file diff --git a/blackberry/src/www/js/telephony.js b/blackberry/src/www/js/telephony.js index 2ad33928..83a2616a 100644 --- a/blackberry/src/www/js/telephony.js +++ b/blackberry/src/www/js/telephony.js @@ -12,7 +12,7 @@ function Telephony() { */ Telephony.prototype.call = function(number) { this.number = number; - Device.exec("call", [this.number]); + device.exec("call", [this.number]); } if (typeof navigator.telephony == "undefined") navigator.telephony = new Telephony(); \ No newline at end of file diff --git a/blackberry/src/www/media/bird.mp3 b/blackberry/src/www/media/bird.mp3 new file mode 100644 index 00000000..a318d348 Binary files /dev/null and b/blackberry/src/www/media/bird.mp3 differ diff --git a/blackberry/src/www/media/percBass2.wav b/blackberry/src/www/media/percBass2.wav new file mode 100644 index 00000000..c0583ec8 Binary files /dev/null and b/blackberry/src/www/media/percBass2.wav differ diff --git a/blackberry/src/www/test/camera.html b/blackberry/src/www/test/camera.html index 95e004bc..5ab4dc77 100644 --- a/blackberry/src/www/test/camera.html +++ b/blackberry/src/www/test/camera.html @@ -9,6 +9,7 @@ function picture() { navigator.camera.getPicture(); alert("Path is: " + navigator.camera.picture); + document.getElementById('testImg').src = navigator.camera.picture; } @@ -18,7 +19,8 @@

Use the Camera

Get the picture


-
+

Placeholder for image:

+


diff --git a/blackberry/src/www/test/contacts.html b/blackberry/src/www/test/contacts.html index ffd1626e..6e230fa1 100644 --- a/blackberry/src/www/test/contacts.html +++ b/blackberry/src/www/test/contacts.html @@ -3,24 +3,49 @@ -

Add a contact with name Joe first

-
-

Find Joe in contacts

+

Find Joe in contacts

+
+

Get first contact in address book

+
+

Use Contact Chooser to select a Contact


-



diff --git a/blackberry/src/www/test/device.html b/blackberry/src/www/test/device.html index 5aa49472..e6b8a81e 100644 --- a/blackberry/src/www/test/device.html +++ b/blackberry/src/www/test/device.html @@ -1,9 +1,10 @@ + diff --git a/blackberry/src/www/test/index.html b/blackberry/src/www/test/index.html index 96d87c59..7d6af463 100644 --- a/blackberry/src/www/test/index.html +++ b/blackberry/src/www/test/index.html @@ -1,12 +1,15 @@ - + + Navigate to Internet (needs MDS)
Test IO (needs MDS)
+
Test Device API
- Test Vibration API
+ Test Notification API
+ Test Media API
Test Telephony API
Test GeoLocation API
Test Camera API
diff --git a/blackberry/src/www/test/location.html b/blackberry/src/www/test/location.html index 2125e680..a43d5519 100644 --- a/blackberry/src/www/test/location.html +++ b/blackberry/src/www/test/location.html @@ -4,27 +4,19 @@ -

Start location module

-

Stop location module

Get current position

-

Map current position




diff --git a/blackberry/src/www/test/media.html b/blackberry/src/www/test/media.html new file mode 100644 index 00000000..0656e686 --- /dev/null +++ b/blackberry/src/www/test/media.html @@ -0,0 +1,17 @@ + + + + + + + +

Play percBass2.wav

+
+

Play bird.mp3

+
+
+
+
+ Back + + \ No newline at end of file diff --git a/blackberry/src/www/test/notification.html b/blackberry/src/www/test/notification.html new file mode 100644 index 00000000..e47aed1d --- /dev/null +++ b/blackberry/src/www/test/notification.html @@ -0,0 +1,18 @@ + + + + + + + +

Vibrate for 10 seconds

+
+

Beep twice!

+
+

Beep ten times!

+
+
+
+ Back + + \ No newline at end of file diff --git a/blackberry/src/www/test/vibration.html b/blackberry/src/www/test/vibration.html deleted file mode 100644 index 1bc98063..00000000 --- a/blackberry/src/www/test/vibration.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - -

Vibrate

-
-
-
-
-
- Back - - \ No newline at end of file diff --git a/javascripts/blackberry/camera.js b/javascripts/blackberry/camera.js new file mode 100644 index 00000000..6eef7181 --- /dev/null +++ b/javascripts/blackberry/camera.js @@ -0,0 +1,30 @@ +/** + * This class provides access to the device camera. + * @constructor + */ +function Camera() { + this.picture = null; +} + +Camera.prototype.launch = function () { + if (device.hasCamera) device.exec("camera", ["obtain"], true); + else alert("Camera not supported on this device."); +} + +/** + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +Camera.prototype.getPicture = function(successCallback, errorCallback, options) { + if (device.hasCamera) { + if (successCallback) this.onSuccess = successCallback; + else this.onSuccess = null; + if (errorCallback) this.onError = errorCallback; + else this.onError = null; + device.exec("camera", ["picture"], true); + } else alert("Camera not supported"); +} + +if (typeof navigator.camera == "undefined") navigator.camera = new Camera(); \ No newline at end of file diff --git a/javascripts/blackberry/contacts.js b/javascripts/blackberry/contacts.js new file mode 100644 index 00000000..43c585d3 --- /dev/null +++ b/javascripts/blackberry/contacts.js @@ -0,0 +1,56 @@ +/** + * This class represents a Contact in the manager. + * @constructor + */ +function Contact() { + this.name = ""; + this.phone = ""; + this.address = ""; + this.email = ""; +} + +function ContactManager() { + this.contacts = []; + this.timestamp = new Date().getTime(); + // Options used when calling ContactManager functions. + this.options = { + 'pageSize':0, + 'pageNumber':0, + 'nameFilter':'' + }; +} +ContactManager.prototype.formParams = function(options, startArray) { + var params = []; + if (startArray) params = startArray; + if (options.pageSize && options.pageSize > 0) params.push("pageSize:" + options.pageSize); + if (options.pageNumber) params.push("pageNumber:" + options.pageNumber); + if (options.nameFilter) params.push("nameFilter:" + options.nameFilter); + return params; +} +ContactManager.prototype.displayContact = function(successCallback, errorCallback, options) { + if (options.nameFilter && options.nameFilter.length > 0) { + var params = ["search"]; + params = this.formParams(options,params); + this.search_onSuccess = successCallback; + this.search_onError = errorCallback; + device.exec("contacts", params, true); + } else { + ContactManager.getAllContacts(successCallback,errorCallback,options); + return; + } +} +ContactManager.prototype.getAllContacts = function(successCallback, errorCallback, options) { + this.global_onSuccess = successCallback; + this.global_onError = errorCallback; + var params = ["getall"]; + params = this.formParams(options,params); + device.exec("contacts", params, true); +} +ContactManager.prototype.chooseContact = function(successCallback, options) { + this.choose_onSuccess = successCallback; + var params = ["choose"]; + params = this.formParams(options,params); + device.exec("contacts", params, true); +} + +if (typeof navigator.ContactManager == "undefined") navigator.ContactManager = new ContactManager(); \ No newline at end of file diff --git a/javascripts/blackberry/device.js b/javascripts/blackberry/device.js new file mode 100644 index 00000000..dc25835d --- /dev/null +++ b/javascripts/blackberry/device.js @@ -0,0 +1,31 @@ +window.device = { + isIPhone: false, + isIPod: false, + isBlackBerry: true, + + init: function() { + this.exec("initialize"); + this.poll(function() { + device.available = typeof device.name == "string"; + }); + }, + exec: function(command, params, sync) { + if (device.available || command == "initialize") { + try { + var cookieCommand = "PhoneGap=" + command; + if (params) cookieCommand += "/" + params.join("/"); + document.cookie = cookieCommand; + if (sync) this.poll(); + } catch(e) { + console.log("Command '" + command + "' has not been executed, because of exception: " + e); + alert("Error executing command '" + command + "'.") + } + } else { + alert("Device not available YET - still loading."); + } + }, + poll: function(callback) { + eval(document.cookie + (callback ? ";callback();" : "")); + } +}; +window.device.init(); \ No newline at end of file diff --git a/javascripts/blackberry/geolocation.js b/javascripts/blackberry/geolocation.js index 399ef9d6..99ba3b06 100644 --- a/javascripts/blackberry/geolocation.js +++ b/javascripts/blackberry/geolocation.js @@ -1,35 +1,81 @@ -Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) { - document.cookie = 'bb_command={command:'+phonegap.LOCATION+'}'; - // Blackberry 4.5 does not let you use function pointers in setInterval. idiots. - geoSuccessCallback = successCallback; - geoErrorCallback = errorCallback; - geoOptions = options; - locationTimeout = window.setInterval('navigator.geolocation._getCurrentPosition()', 1000); +/** + * This class provides access to device GPS data. + * @constructor + */ +function Geolocation() { + /** + * Was the GPS started? + */ + this.started = false; + + /** + * The last known GPS position. + */ + this.lastPosition = null; +} + +/** + * Starts the GPS of the device + */ +Geolocation.prototype.start = function() { + if (this.started) { + alert("GPS already started"); + return; + } + device.exec("location", ["start"], true); +} + +/** + * Stops the GPS of the device + */ +Geolocation.prototype.stop = function() { + if (!this.started) { + alert("GPS not started"); + return; + } + if (this.locationTimeout) window.clearTimeout(this.locationTimeout); + device.exec("location", ["stop"], true); } -Geolocation.prototype._getCurrentPosition = function(successCallback, errorCallback, options) { - var cookies = document.cookie.split(';'); - for (var i=0; i + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.webBrowser = new System.Windows.Forms.WebBrowser(); + this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); + this.SuspendLayout(); + // + // webBrowser + // + this.webBrowser.Dock = System.Windows.Forms.DockStyle.Fill; + this.webBrowser.Location = new System.Drawing.Point(0, 0); + this.webBrowser.Name = "webBrowser"; + this.webBrowser.Size = new System.Drawing.Size(176, 200); + this.webBrowser.Navigating += new System.Windows.Forms.WebBrowserNavigatingEventHandler(this.webBrowser_Navigating); + // + // openFileDialog1 + // + this.openFileDialog1.FileName = "openFileDialog1"; + // + // WebForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.AutoScroll = true; + this.ClientSize = new System.Drawing.Size(176, 200); + this.Controls.Add(this.webBrowser); + this.Name = "WebForm"; + this.Text = "WebForm"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.WebBrowser webBrowser; + private CommandManager commandManager; + private System.Windows.Forms.OpenFileDialog openFileDialog1; + } +} \ No newline at end of file diff --git a/winmo/WebForm.cs b/winmo/WebForm.cs new file mode 100644 index 00000000..3e767fcc --- /dev/null +++ b/winmo/WebForm.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using System.IO; +using System.Reflection; + +namespace PhoneGap { + + public partial class WebForm : Form { + // TODO: Shouldn't create the HTML to display from resources all in memory. Should create a file and send html/js/css resources to the file. + // Less memory used. + public WebForm() { + // use this for certain file/audio i/o operations - grab embedded resources and add + // dynamically to the manifestmodule. Cool hack - thanks Ran! + //string s = Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName; + + InitializeComponent(); + commandManager = new CommandManager(); + webBrowser.ScriptErrorsSuppressed = false; + webBrowser.DocumentText = parseDataProtocol(readEmbedded("/www/index.html")); + } + + private void webBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e) { + if (e.Url.Host.Equals("gap.exec")) { + e.Cancel = true; + String res = commandManager.processInstruction(e.Url.AbsolutePath); + webBrowser.Navigate(new Uri("javascript:" + res + ";abc.x=1;//JS error!")); + } + } + private String readEmbedded(String fileName) + { + Assembly assembly = Assembly.GetExecutingAssembly(); + String path = assembly.GetName().Name + ".www." + (fileName.StartsWith("/www/") ? fileName.Substring(5) : fileName).Replace("/", "."); + Stream stream = assembly.GetManifestResourceStream(path); + StreamReader reader = new StreamReader(stream, Encoding.GetEncoding("UTF-8")); + return reader.ReadToEnd(); + } + + private String parseDataProtocol(String documentText) { + string toMatch = "",position); + int scriptTagLength = endScript + "".Length - position; + String jsName = parsedText.Substring(0, endName - 2); + parsedText = documentText.Remove(position, scriptTagLength); + parsedText = parsedText.Insert(position, ""); + return parseDataProtocol(parsedText); + } + return documentText; + } + } + +} \ No newline at end of file diff --git a/winmo/WebForm.resx b/winmo/WebForm.resx new file mode 100644 index 00000000..8c326d74 --- /dev/null +++ b/winmo/WebForm.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + Pocket_PC + + + True + + \ No newline at end of file diff --git a/winmo/www/index.html b/winmo/www/index.html new file mode 100644 index 00000000..94ce2c35 --- /dev/null +++ b/winmo/www/index.html @@ -0,0 +1,32 @@ + + + + Nothing important + + + + + +

This is an HTML page.


+ Show me the device model/name
+ Show me the device UUID
+ Play applause
+ Play birds
+
+

+
+

For more information, please visit www.phonegap.com.

+

PhoneGap source is available on GitHub (http://www.github.com/sintaxi/phonegap/tree).

+ + \ No newline at end of file diff --git a/winmo/www/js/device.js b/winmo/www/js/device.js new file mode 100644 index 00000000..021aa0d7 --- /dev/null +++ b/winmo/www/js/device.js @@ -0,0 +1,20 @@ +var device = { + init: function() { + this.exec("initialize"); + // For some reason, in WinMo v6.0, we need to delay setting device.available because device.name does not exist yet. + // A 10ms delay is sufficient for the variable to be visible. + setTimeout('device.available = typeof(device.name) == "string";',10); + }, + exec: function(command, params) { + if (device.available || command == "initialize") { + try { + var url = "http://gap.exec/" + command; + if (params) url += "/" + params.join("/"); + window.location.href = url; + } catch(e) { + console.log("Command '" + command + "' has not been executed, because of exception: " + e); + alert("Error executing command '" + command + "'."); + } + } + } +}; \ No newline at end of file diff --git a/winmo/www/js/media.js b/winmo/www/js/media.js new file mode 100644 index 00000000..e3792d13 --- /dev/null +++ b/winmo/www/js/media.js @@ -0,0 +1,5 @@ +var media = { + playSound: function(filename) { + device.exec("media",[filename]); + } +}; \ No newline at end of file diff --git a/winmo/www/media/applause.wav b/winmo/www/media/applause.wav new file mode 100644 index 00000000..56d29afb Binary files /dev/null and b/winmo/www/media/applause.wav differ diff --git a/winmo/www/media/bird.mp3 b/winmo/www/media/bird.mp3 new file mode 100644 index 00000000..a318d348 Binary files /dev/null and b/winmo/www/media/bird.mp3 differ