You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by de...@apache.org on 2012/02/08 21:14:02 UTC
[16/19] CB-226 Rename to Cordova.
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/media/CaptureMode.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/media/CaptureMode.java b/framework/ext/src/org/apache/cordova/media/CaptureMode.java
new file mode 100644
index 0000000..95bc384
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/media/CaptureMode.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.media;
+
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+
+public class CaptureMode {
+
+ private String mimeType = null;
+ private long height = 0;
+ private long width = 0;
+
+ public CaptureMode() {
+ }
+
+ public CaptureMode(String type) {
+ this.mimeType = type;
+ }
+
+ public CaptureMode(String type, long width, long height) {
+ this.mimeType = type;
+ this.height = height;
+ this.width = width;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public long getHeight() {
+ return height;
+ }
+
+ public long getWidth() {
+ return width;
+ }
+
+ public JSONObject toJSONObject() {
+ JSONObject o = new JSONObject();
+ try {
+ o.put("type", getMimeType());
+ o.put("height", getHeight());
+ o.put("width", getWidth());
+ }
+ catch (JSONException ignored) {
+ }
+ return o;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof CaptureMode)) {
+ return false;
+ }
+ CaptureMode cm = (CaptureMode)o;
+ return ((mimeType == null ? cm.mimeType == null :
+ mimeType.equals(cm.mimeType))
+ && (width == cm.width)
+ && (height == cm.height));
+ }
+
+ public int hashCode() {
+ int hash = (mimeType != null ? mimeType.hashCode() : 19);
+ hash = 37*hash + (int)width;
+ hash = 37*hash + (int)height;
+ return hash;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/media/CaptureOperation.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/media/CaptureOperation.java b/framework/ext/src/org/apache/cordova/media/CaptureOperation.java
new file mode 100644
index 0000000..ab647c3
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/media/CaptureOperation.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.media;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.apache.cordova.file.File;
+import org.apache.cordova.file.FileUtils;
+import org.apache.cordova.util.Logger;
+
+public abstract class CaptureOperation implements Runnable {
+ // max number of media files to capture
+ protected int limit = 1;
+
+ // for sending results
+ protected String callbackId = null;
+
+ // list of captured media files
+ protected final Vector captureFiles = new Vector();
+
+ // media file queue
+ protected MediaQueue mediaQueue = null;
+
+ // used to interrupt thread
+ protected volatile Thread myThread;
+
+ // to determine if operation has been canceled
+ protected boolean canceled = false;
+
+ /**
+ * Creates and starts a capture operation on a new thread.
+ *
+ * @param limit
+ * maximum number of media files to capture
+ * @param callbackId
+ * the callback to receive the files
+ * @param queue
+ * the queue from which to retrieve captured media files
+ */
+ public CaptureOperation(int limit, String callbackId, MediaQueue queue) {
+ if (limit > 1) {
+ this.limit = limit;
+ }
+
+ this.callbackId = callbackId;
+ this.mediaQueue = queue;
+ this.myThread = new Thread(this);
+ }
+
+ /**
+ * Waits for media file to be captured.
+ */
+ public void run() {
+ if (myThread == null) {
+ return; // stopped before started
+ }
+
+ Logger.log(this.getClass().getName() + ": " + callbackId + " started");
+
+ // tasks to be run before entering main loop
+ setup();
+
+ // capture until interrupted or we've reached capture limit
+ Thread thisThread = Thread.currentThread();
+ String filePath = null;
+ while (myThread == thisThread && captureFiles.size() < limit) {
+ try {
+ // consume file added to media capture queue
+ filePath = mediaQueue.remove();
+ }
+ catch (InterruptedException e) {
+ Logger.log(this.getClass().getName() + ": " + callbackId + " interrupted");
+ // and we're done
+ break;
+ }
+ processFile(filePath);
+ }
+
+ // perform cleanup tasks
+ teardown();
+
+ // process captured results
+ processResults();
+
+ // unregister the operation from the controller
+ CaptureControl.getCaptureControl().removeCaptureOperation(this);
+
+ Logger.log(this.getClass().getName() + ": " + callbackId + " finished");
+ }
+
+ /**
+ * Starts this capture operation on a new thread.
+ */
+ protected void start() {
+ if (myThread == null) {
+ return; // stopped before started
+ }
+ myThread.start();
+ }
+
+ /**
+ * Stops the operation.
+ */
+ public void stop() {
+ // interrupt capture thread
+ Thread tmpThread = myThread;
+ myThread = null;
+ if (tmpThread != null && tmpThread.isAlive()) {
+ tmpThread.interrupt();
+ }
+ }
+
+ /**
+ * Cancels the operation.
+ */
+ public void cancel() {
+ canceled = true;
+ stop();
+ }
+
+ /**
+ * Processes the results of the capture operation.
+ */
+ protected void processResults() {
+ // process results
+ if (!canceled) {
+ // invoke appropriate callback
+ if (captureFiles.size() > 0) {
+ // send capture files
+ MediaCapture.captureSuccess(captureFiles, callbackId);
+ }
+ else {
+ // error
+ MediaCapture.captureError(callbackId);
+ }
+ }
+ else {
+ removeCaptureFiles();
+ }
+ }
+
+ /**
+ * Adds a media file to list of collected media files for this operation.
+ *
+ * @param file
+ * object containing media file properties
+ */
+ protected void addCaptureFile(File file) {
+ captureFiles.addElement(file);
+ }
+
+ /**
+ * Removes captured files from the file system.
+ */
+ protected void removeCaptureFiles() {
+ for (Enumeration e = captureFiles.elements(); e.hasMoreElements();) {
+ File file = (File) e.nextElement();
+ try {
+ FileUtils.delete(file.getFullPath());
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Override this method to perform tasks before the operation starts.
+ */
+ protected void setup() {
+ }
+
+ /**
+ * Override this method to perform tasks after the operation has
+ * stopped.
+ */
+ protected void teardown() {
+ }
+
+ /**
+ * Subclasses must implement this method to process a captured media file.
+ * @param filePath the full path of the media file
+ */
+ protected abstract void processFile(final String filePath);
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/media/ImageCaptureListener.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/media/ImageCaptureListener.java b/framework/ext/src/org/apache/cordova/media/ImageCaptureListener.java
new file mode 100644
index 0000000..4490c27
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/media/ImageCaptureListener.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.media;
+
+import net.rim.device.api.io.file.FileSystemJournal;
+import net.rim.device.api.io.file.FileSystemJournalEntry;
+import net.rim.device.api.io.file.FileSystemJournalListener;
+
+/**
+ * Listens for image files that are added to file system.
+ * <p>
+ * The file system notifications will arrive on the application event thread.
+ * When it receives a notification, it adds the image file path to a MediaQueue
+ * so that the capture thread can process the file.
+ */
+class ImageCaptureListener implements FileSystemJournalListener {
+
+ /**
+ * Used to track file system changes.
+ */
+ private long lastUSN = 0;
+
+ /**
+ * Collection of media files.
+ */
+ private MediaQueue queue = null;
+
+ /**
+ * Constructor.
+ */
+ ImageCaptureListener(MediaQueue queue) {
+ this.queue = queue;
+ }
+
+ /**
+ * Listens for file system changes. When a JPEG file is added, we process
+ * it and send it back.
+ */
+ public void fileJournalChanged()
+ {
+ // next sequence number file system will use
+ long USN = FileSystemJournal.getNextUSN();
+
+ for (long i = USN - 1; i >= lastUSN && i < USN; --i)
+ {
+ FileSystemJournalEntry entry = FileSystemJournal.getEntry(i);
+ if (entry == null)
+ {
+ break;
+ }
+
+ if (entry.getEvent() == FileSystemJournalEntry.FILE_ADDED)
+ {
+ String path = entry.getPath();
+ if (path != null && path.indexOf(".jpg") != -1)
+ {
+ // add file path to the capture queue
+ queue.add("file://" + path);
+ break;
+ }
+ }
+ }
+
+ // remember the file journal change number,
+ // so we don't search the same events again and again
+ lastUSN = USN;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/media/ImageCaptureOperation.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/media/ImageCaptureOperation.java b/framework/ext/src/org/apache/cordova/media/ImageCaptureOperation.java
new file mode 100644
index 0000000..980a1dd
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/media/ImageCaptureOperation.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.media;
+
+import java.io.IOException;
+import java.util.Date;
+import javax.microedition.io.Connector;
+import javax.microedition.io.file.FileConnection;
+
+import org.apache.cordova.file.File;
+import org.apache.cordova.file.FileUtils;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.io.MIMETypeAssociations;
+import net.rim.device.api.ui.UiApplication;
+
+public class ImageCaptureOperation extends CaptureOperation {
+ // content type
+ public static String CONTENT_TYPE = "image/";
+
+ // file system listener
+ private ImageCaptureListener listener = null;
+
+ /**
+ * Creates and starts an image capture operation.
+ *
+ * @param limit
+ * maximum number of media files to capture
+ * @param callbackId
+ * the callback to receive the files
+ * @param queue
+ * the queue from which to retrieve captured media files
+ */
+ public ImageCaptureOperation(int limit, String callbackId, MediaQueue queue) {
+ super(limit, callbackId, queue);
+
+ // listener to capture image files added to file system
+ this.listener = new ImageCaptureListener(queue);
+
+ start();
+ }
+
+ /**
+ * Registers file system listener and launches native camera application.
+ */
+ protected void setup() {
+ // register listener for files being written
+ synchronized(UiApplication.getEventLock()) {
+ UiApplication.getUiApplication().addFileSystemJournalListener(listener);
+ }
+
+ // launch the native camera application
+ CameraControl.launchCamera();
+ }
+
+ /**
+ * Unregisters file system listener and closes native camera application.
+ */
+ protected void teardown() {
+ // remove file system listener
+ synchronized(UiApplication.getEventLock()) {
+ UiApplication.getUiApplication().removeFileSystemJournalListener(listener);
+ }
+
+ // close the native camera application
+ CameraControl.closeCamera();
+ }
+
+ /**
+ * Waits for image file to be written to file system and retrieves its file
+ * properties.
+ *
+ * @param filePath
+ * the full path of the media file
+ */
+ protected void processFile(final String filePath) {
+ Logger.log(this.getClass().getName() + ": processing file: " + filePath);
+
+ // wait for file to finish writing and add it to captured files
+ addCaptureFile(getMediaFile(filePath));
+ }
+
+ /**
+ * Waits for file to be fully written to the file system before retrieving
+ * its file properties.
+ *
+ * @param filePath
+ * Full path of the image file
+ * @throws IOException
+ */
+ private File getMediaFile(String filePath) {
+ File file = new File(FileUtils.stripSeparator(filePath));
+
+ // time begin waiting for file write
+ long start = (new Date()).getTime();
+
+ // wait for the file to be fully written, then grab its properties
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection) Connector.open(filePath, Connector.READ);
+ if (fconn.exists()) {
+ // wait for file to be fully written
+ long fileSize = fconn.fileSize();
+ long size = 0;
+ Thread thisThread = Thread.currentThread();
+ while (myThread == thisThread) {
+ try {
+ Thread.sleep(100);
+ }
+ catch (InterruptedException e) {
+ break;
+ }
+ size = fconn.fileSize();
+ if (size == fileSize) {
+ break;
+ }
+ fileSize = size;
+ }
+ Logger.log(this.getClass().getName() + ": " + filePath + " size="
+ + Long.toString(fileSize) + " bytes");
+
+ // retrieve file properties
+ file.setLastModifiedDate(fconn.lastModified());
+ file.setName(FileUtils.stripSeparator(fconn.getName()));
+ file.setSize(fileSize);
+ file.setType(MIMETypeAssociations.getMIMEType(filePath));
+ }
+ }
+ catch (IOException e) {
+ Logger.log(this.getClass().getName() + ": " + e);
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ } catch (IOException ignored) {}
+ }
+
+ // log time it took to write the file
+ long end = (new Date()).getTime();
+ Logger.log(this.getClass().getName() + ": wait time="
+ + Long.toString(end - start) + " ms");
+
+ return file;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/media/MediaCapture.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/media/MediaCapture.java b/framework/ext/src/org/apache/cordova/media/MediaCapture.java
new file mode 100644
index 0000000..326757a
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/media/MediaCapture.java
@@ -0,0 +1,504 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.media;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.microedition.media.Manager;
+
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.file.File;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.util.Logger;
+import org.apache.cordova.util.StringUtils;
+
+/**
+ * This plugin provides the ability to capture media from the native media
+ * applications. The appropriate media application is launched, and a capture
+ * operation is started in the background to identify captured media files and
+ * return the file info back to the caller.
+ */
+public class MediaCapture extends Plugin {
+
+ public static String PROTOCOL_CAPTURE = "capture";
+
+ /**
+ * Error codes.
+ */
+ // Camera or microphone failed to capture image or sound.
+ private static final int CAPTURE_INTERNAL_ERR = 0;
+ // Camera application or audio capture application is currently serving other capture request.
+ private static final int CAPTURE_APPLICATION_BUSY = 1;
+ // Invalid use of the API (e.g. limit parameter has value less than one).
+ private static final int CAPTURE_INVALID_ARGUMENT = 2;
+ // User exited camera application or audio capture application before capturing anything.
+ private static final int CAPTURE_NO_MEDIA_FILES = 3;
+ // The requested capture operation is not supported.
+ private static final int CAPTURE_NOT_SUPPORTED = 20;
+
+ /**
+ * Possible actions.
+ */
+ protected static final int ACTION_GET_SUPPORTED_AUDIO_MODES = 0;
+ protected static final int ACTION_GET_SUPPORTED_IMAGE_MODES = 1;
+ protected static final int ACTION_GET_SUPPORTED_VIDEO_MODES = 2;
+ protected static final int ACTION_CAPTURE_AUDIO = 3;
+ protected static final int ACTION_CAPTURE_IMAGE = 4;
+ protected static final int ACTION_CAPTURE_VIDEO = 5;
+ protected static final int ACTION_CANCEL_CAPTURES = 6;
+
+ /**
+ * Executes the requested action and returns a PluginResult.
+ *
+ * @param action
+ * The action to execute.
+ * @param callbackId
+ * The callback ID to be invoked upon action completion
+ * @param args
+ * JSONArry of arguments for the action.
+ * @return A PluginResult object with a status and message.
+ */
+ public PluginResult execute(String action, JSONArray args, String callbackId) {
+
+ switch (getAction(action)) {
+ case ACTION_GET_SUPPORTED_AUDIO_MODES:
+ return getAudioCaptureModes();
+ case ACTION_GET_SUPPORTED_IMAGE_MODES:
+ return getImageCaptureModes();
+ case ACTION_GET_SUPPORTED_VIDEO_MODES:
+ return getVideoCaptureModes();
+ case ACTION_CAPTURE_AUDIO:
+ return captureAudio(args, callbackId);
+ case ACTION_CAPTURE_IMAGE:
+ return captureImage(args, callbackId);
+ case ACTION_CAPTURE_VIDEO:
+ return captureVideo(args, callbackId);
+ case ACTION_CANCEL_CAPTURES:
+ CaptureControl.getCaptureControl().stopPendingOperations(true);
+ return new PluginResult(PluginResult.Status.OK);
+ }
+
+ return new PluginResult(PluginResult.Status.INVALID_ACTION,
+ "MediaCapture: invalid action " + action);
+ }
+
+ /**
+ * Determines if audio capture is supported.
+ * @return <code>true</code> if audio capture is supported
+ */
+ protected boolean isAudioCaptureSupported() {
+ return (System.getProperty("supports.audio.capture").equals(Boolean.TRUE.toString())
+ && AudioControl.hasAudioRecorderApplication());
+ }
+
+ /**
+ * Determines if video capture is supported.
+ * @return <code>true</code> if video capture is supported
+ */
+ protected boolean isVideoCaptureSupported() {
+ return (System.getProperty("supports.video.capture").equals(Boolean.TRUE.toString()));
+ }
+
+ /**
+ * Retrieves supported audio capture modes (content types).
+ * @return supported audio capture modes
+ */
+ protected PluginResult getAudioCaptureModes() {
+ if (!isAudioCaptureSupported()) {
+ // if audio capture is not supported, return an empty array
+ // of capture modes
+ Logger.log(this.getClass().getName() + ": audio capture not supported");
+ return new PluginResult(PluginResult.Status.OK, "[]");
+ }
+
+ // get all supported capture content types
+ String[] contentTypes = getCaptureContentTypes();
+
+ // return audio content types only
+ JSONArray modes = new JSONArray();
+ for (int i = 0; i < contentTypes.length; i++) {
+ if (contentTypes[i].startsWith(AudioCaptureOperation.CONTENT_TYPE)) {
+ modes.add(new CaptureMode(contentTypes[i]).toJSONObject());
+ }
+ }
+
+ return new PluginResult(PluginResult.Status.OK, modes.toString());
+ }
+
+ /**
+ * Retrieves supported image capture modes (content type, width and height).
+ * @return supported image capture modes
+ */
+ protected PluginResult getImageCaptureModes() {
+ // get supported capture content types
+ String[] contentTypes = getCaptureContentTypes();
+
+ // need to get the recording dimensions from supported image encodings
+ String imageEncodings = System.getProperty("video.snapshot.encodings");
+ Logger.log(this.getClass().getName() + ": video.snapshot.encodings=" + imageEncodings);
+ String[] encodings = StringUtils.split(imageEncodings, "encoding=");
+
+ // find matching encodings and parse them for dimensions
+ // it's so annoying that we have to do this
+ CaptureMode mode = null;
+ Vector list = new Vector();
+ JSONArray modes = new JSONArray();
+ for (int i = 0; i < contentTypes.length; i++) {
+ if (contentTypes[i].startsWith(ImageCaptureOperation.CONTENT_TYPE)) {
+ String type = contentTypes[i].substring(ImageCaptureOperation.CONTENT_TYPE.length());
+ for (int j = 0; j < encodings.length; j++) {
+ // format: "jpeg&width=2592&height=1944 "
+ String enc = encodings[j];
+ if (enc.startsWith(type)) {
+ Hashtable parms = parseEncodingString(enc);
+ // "width="
+ String w = (String)parms.get("width");
+ long width = (w == null) ? 0 : Long.parseLong(w);
+ // "height="
+ String h = (String)parms.get("height");
+ long height = (h == null) ? 0 : Long.parseLong(h);
+ // new capture mode
+ mode = new CaptureMode(contentTypes[i], width, height);
+ // don't want duplicates
+ if (!list.contains(mode)) {
+ list.addElement(mode);
+ modes.add(mode.toJSONObject());
+ }
+ }
+ }
+ }
+ }
+
+ return new PluginResult(PluginResult.Status.OK, modes.toString());
+ }
+
+ /**
+ * Retrieves supported video capture modes (content type, width and height).
+ * @return supported video capture modes
+ */
+ protected PluginResult getVideoCaptureModes() {
+ if (!isVideoCaptureSupported()) {
+ // if the device does not support video capture, return an empty
+ // array of capture modes
+ Logger.log(this.getClass().getName() + ": video capture not supported");
+ return new PluginResult(PluginResult.Status.OK, "[]");
+ }
+
+ /**
+ * DOH! Even if video capture is supported, BlackBerry's API
+ * does not provide any 'video/' content types for the 'capture'
+ * protocol. So if we looked at only capture content types,
+ * it wouldn't return any results...
+ *
+ * // get all supported capture content types
+ * String[] contentTypes = getCaptureContentTypes();
+ *
+ * A better alternative, and probably not too inaccurate, would be to
+ * send back all supported video modes (not just capture). This will
+ * at least give the developer an idea of the capabilities.
+ */
+
+ // retrieve ALL supported video encodings
+ String videoEncodings = System.getProperty("video.encodings");
+ Logger.log(this.getClass().getName() + ": video.encodings=" + videoEncodings);
+ String[] encodings = StringUtils.split(videoEncodings, "encoding=");
+
+ // parse them into CaptureModes
+ JSONArray modes = new JSONArray();
+ String enc = null;
+ CaptureMode mode = null;
+ Vector list = new Vector();
+ for (int i = 0; i < encodings.length; i++) {
+ enc = encodings[i];
+ // format: "video/3gpp&width=640&height=480&video_codec=MPEG-4&audio_codec=AAC "
+ if (enc.startsWith(VideoCaptureOperation.CONTENT_TYPE)) {
+ Hashtable parms = parseEncodingString(enc);
+ // type "video/3gpp"
+ String t = (String)parms.get("type");
+ // "width="
+ String w = (String)parms.get("width");
+ long width = (w == null) ? 0 : Long.parseLong(w);
+ // "height="
+ String h = (String)parms.get("height");
+ long height = (h == null) ? 0 : Long.parseLong(h);
+ // new capture mode
+ mode = new CaptureMode(t, width, height);
+ // don't want duplicates
+ if (!list.contains(mode)) {
+ list.addElement(mode);
+ modes.add(mode.toJSONObject());
+ }
+ }
+ }
+
+ return new PluginResult(PluginResult.Status.OK, modes.toString());
+ }
+
+ /**
+ * Utility method to parse encoding strings.
+ *
+ * @param encodingString
+ * encoding string
+ * @return Hashtable containing key:value pairs
+ */
+ protected Hashtable parseEncodingString(final String encodingString) {
+ // format: "video/3gpp&width=640&height=480&video_codec=MPEG-4&audio_codec=AAC "
+ Hashtable props = new Hashtable();
+ String[] parms = StringUtils.split(encodingString, "&");
+ props.put("type", parms[0]);
+ for (int i = 0; i < parms.length; i++) {
+ String parameter = parms[i];
+ if (parameter.indexOf('=') != -1) {
+ String[] pair = StringUtils.split(parameter, "=");
+ props.put(pair[0].trim(), pair[1].trim());
+ }
+ }
+ return props;
+ }
+
+ /**
+ * Returns the content types supported for the <code>capture://</code>
+ * protocol.
+ *
+ * @return list of supported capture content types
+ */
+ protected static String[] getCaptureContentTypes() {
+ // retrieve list of all content types supported for capture protocol
+ return Manager.getSupportedContentTypes(PROTOCOL_CAPTURE);
+ }
+
+ /**
+ * Starts an audio capture operation using the native voice notes recorder
+ * application. If the native voice notes recorder application is already
+ * running, the <code>CAPTURE_APPLICATION_BUSY</code> error is returned.
+ *
+ * @param args
+ * capture options (e.g., limit)
+ * @param callbackId
+ * the callback to be invoked with the capture results
+ * @return PluginResult containing captured media file properties
+ */
+ protected PluginResult captureAudio(final JSONArray args, final String callbackId) {
+ PluginResult result = null;
+
+ // if audio is not being recorded, start audio capture
+ if (!AudioControl.hasAudioRecorderApplication()) {
+ Logger.log(this.getClass().getName()
+ + ": Audio recorder application is not installed.");
+ result = new PluginResult(PluginResult.Status.ERROR,
+ CAPTURE_NOT_SUPPORTED);
+ }
+ else if (AudioControl.isAudioRecorderActive()) {
+ Logger.log(this.getClass().getName()
+ + ": Audio recorder application is busy.");
+ result = new PluginResult(PluginResult.Status.ERROR,
+ CAPTURE_APPLICATION_BUSY);
+ }
+ else {
+ // optional parameters
+ int limit = args.optInt(0, 1);
+ long duration = args.optLong(1, 0);
+
+ // start audio capture
+ // start capture operation in the background
+ CaptureControl.getCaptureControl().startAudioCaptureOperation(
+ limit, duration, callbackId);
+
+ // return NO_RESULT and allow callbacks to be invoked later
+ result = new PluginResult(PluginResult.Status.NO_RESULT);
+ result.setKeepCallback(true);
+ }
+
+ return result;
+ }
+
+ /**
+ * Starts an image capture operation using the native camera application. If
+ * the native camera application is already running, the
+ * <code>CAPTURE_APPLICATION_BUSY</code> error is returned.
+ *
+ * @param args
+ * capture options (e.g., limit)
+ * @param callbackId
+ * the callback to be invoked with the capture results
+ * @return PluginResult containing captured media file properties
+ */
+ protected PluginResult captureImage(final JSONArray args,
+ final String callbackId) {
+ PluginResult result = null;
+
+ if (CameraControl.isCameraActive()) {
+ Logger.log(this.getClass().getName()
+ + ": Camera application is busy.");
+ result = new PluginResult(PluginResult.Status.ERROR,
+ CAPTURE_APPLICATION_BUSY);
+ }
+ else {
+ // optional parameters
+ int limit = args.optInt(0, 1);
+
+ // start capture operation in the background
+ CaptureControl.getCaptureControl().startImageCaptureOperation(
+ limit, callbackId);
+
+ // return NO_RESULT and allow callbacks to be invoked later
+ result = new PluginResult(PluginResult.Status.NO_RESULT);
+ result.setKeepCallback(true);
+ }
+
+ return result;
+ }
+
+ /**
+ * Starts an video capture operation using the native video recorder
+ * application. If the native video recorder application is already running,
+ * the <code>CAPTURE_APPLICATION_BUSY</code> error is returned.
+ *
+ * @param args
+ * capture options (e.g., limit)
+ * @param callbackId
+ * the callback to be invoked with the capture results
+ * @return PluginResult containing captured media file properties
+ */
+ protected PluginResult captureVideo(final JSONArray args,
+ final String callbackId) {
+ PluginResult result = null;
+
+ if (!isVideoCaptureSupported()) {
+ Logger.log(this.getClass().getName()
+ + ": Video capture is not supported.");
+ result = new PluginResult(PluginResult.Status.ERROR,
+ CAPTURE_NOT_SUPPORTED);
+ }
+ else if (CameraControl.isVideoRecorderActive()) {
+ Logger.log(this.getClass().getName()
+ + ": Video recorder application is busy.");
+ result = new PluginResult(PluginResult.Status.ERROR,
+ CAPTURE_APPLICATION_BUSY);
+ }
+ else {
+ // optional parameters
+ int limit = args.optInt(0, 1);
+
+ // start capture operation in the background
+ CaptureControl.getCaptureControl().startVideoCaptureOperation(
+ limit, callbackId);
+
+ // return NO_RESULT and allow callbacks to be invoked later
+ result = new PluginResult(PluginResult.Status.NO_RESULT);
+ result.setKeepCallback(true);
+ }
+
+ return result;
+ }
+
+ /**
+ * Sends media capture result back to JavaScript.
+ *
+ * @param mediaFiles
+ * list of File objects describing captured media files
+ * @param callbackId
+ * the callback to receive the file descriptions
+ */
+ public static void captureSuccess(Vector mediaFiles, String callbackId) {
+ PluginResult result = null;
+ File file = null;
+
+ JSONArray array = new JSONArray();
+ for (Enumeration e = mediaFiles.elements(); e.hasMoreElements();) {
+ file = (File) e.nextElement();
+ array.add(file.toJSONObject());
+ }
+
+ // invoke the appropriate callback
+ result = new PluginResult(PluginResult.Status.OK, array.toString());
+ success(result, callbackId);
+ }
+
+ /**
+ * Sends error back to JavaScript.
+ *
+ * @param callbackId
+ * the callback to receive the error
+ */
+ public static void captureError(String callbackId) {
+ error(new PluginResult(PluginResult.Status.ERROR,
+ CAPTURE_NO_MEDIA_FILES), callbackId);
+ }
+
+ /**
+ * Called when application is resumed.
+ */
+ public void onResume() {
+ // We launch the native media applications for capture operations, which
+ // puts this application in the background. This application will come
+ // to the foreground when the user closes the native media application.
+ // So we close any running capture operations any time we resume.
+ //
+ // It would be nice if we could catch the EVT_APP_FOREGROUND event that
+ // is supposed to be triggered when the application comes to the
+ // foreground, but have not seen a way to do that on the Java side.
+ // Unfortunately, we have to get notification from the JavaScript side,
+ // which does get the event. (Argh! Only BlackBerry.)
+ //
+ // In this case, we're just stopping the capture operations, not
+ // canceling them.
+ CaptureControl.getCaptureControl().stopPendingOperations(false);
+ }
+
+ /**
+ * Invoked when this application terminates.
+ */
+ public void onDestroy() {
+ CaptureControl.getCaptureControl().stopPendingOperations(true);
+ }
+
+ /**
+ * Returns action to perform.
+ * @param action
+ * @return action to perform
+ */
+ protected static int getAction(String action) {
+ if ("getSupportedAudioModes".equals(action)) {
+ return ACTION_GET_SUPPORTED_AUDIO_MODES;
+ }
+ if ("getSupportedImageModes".equals(action)) {
+ return ACTION_GET_SUPPORTED_IMAGE_MODES;
+ }
+ if ("getSupportedVideoModes".equals(action)) {
+ return ACTION_GET_SUPPORTED_VIDEO_MODES;
+ }
+ if ("captureAudio".equals(action)) {
+ return ACTION_CAPTURE_AUDIO;
+ }
+ if ("captureImage".equals(action)) {
+ return ACTION_CAPTURE_IMAGE;
+ }
+ if ("captureVideo".equals(action)) {
+ return ACTION_CAPTURE_VIDEO;
+ }
+ if ("stopCaptures".equals(action)) {
+ return ACTION_CANCEL_CAPTURES;
+ }
+ return -1;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/media/MediaQueue.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/media/MediaQueue.java b/framework/ext/src/org/apache/cordova/media/MediaQueue.java
new file mode 100644
index 0000000..d5f5f63
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/media/MediaQueue.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.media;
+
+import java.util.Vector;
+
+/**
+ * Acts as a container for captured media files. The media applications will
+ * add to the queue when a media file is captured.
+ */
+class MediaQueue {
+ private Vector queue = new Vector();
+
+ synchronized void add(final String filePath) {
+ queue.addElement(filePath);
+ notifyAll();
+ }
+
+ synchronized String remove() throws InterruptedException {
+ while (queue.size() == 0) {
+ wait();
+ }
+ String filePath = (String) queue.firstElement();
+ queue.removeElement(filePath);
+ notifyAll();
+ return filePath;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/media/VideoCaptureListener.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/media/VideoCaptureListener.java b/framework/ext/src/org/apache/cordova/media/VideoCaptureListener.java
new file mode 100644
index 0000000..269504c
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/media/VideoCaptureListener.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.media;
+
+import net.rim.device.api.io.file.FileSystemJournal;
+import net.rim.device.api.io.file.FileSystemJournalEntry;
+import net.rim.device.api.io.file.FileSystemJournalListener;
+
+/**
+ * Listens for video recording files that are added to file system.
+ * <p>
+ * Video recordings are added to the file system in a multi-step process. The
+ * video recorder application records the video on a background thread. While
+ * the recording is in progress, it is added to the file system with a '.lock'
+ * extension. When the user stops the recording, the file is renamed to the
+ * video recorder extension (e.g. .3GP). Therefore, we listen for the
+ * <code>FileSystemJournalEntry.FILE_RENAMED</code> event, capturing when the
+ * new path name ends in the video recording file extension.
+ * <p>
+ * The file system notifications will arrive on the application event thread.
+ * When it receives a notification, it adds the image file path to a MediaQueue
+ * so that the capture thread can process the file.
+ */
+class VideoCaptureListener implements FileSystemJournalListener {
+
+ /**
+ * Used to track file system changes.
+ */
+ private long lastUSN = 0;
+
+ /**
+ * Queue to send media files to for processing.
+ */
+ private MediaQueue queue = null;
+
+ /**
+ * Newly added video recording.
+ */
+ private String newFilePath = null;
+
+ /**
+ * Constructor.
+ */
+ VideoCaptureListener(MediaQueue queue) {
+ this.queue = queue;
+ }
+
+ public void fileJournalChanged() {
+ // next sequence number file system will use
+ long USN = FileSystemJournal.getNextUSN();
+
+ for (long i = USN - 1; i >= lastUSN && i < USN; --i)
+ {
+ FileSystemJournalEntry entry = FileSystemJournal.getEntry(i);
+ if (entry == null)
+ {
+ break;
+ }
+
+ String path = entry.getPath();
+ if (entry.getEvent() == FileSystemJournalEntry.FILE_ADDED
+ && newFilePath == null) {
+ // a new file has been added to the file system
+ // if it has a video recording extension, store it until
+ // it is renamed, indicating it has finished being written to
+ int index = path.indexOf(".3GP");
+ if (index != -1) {
+ newFilePath = path.substring(0, index + 4);
+ }
+ }
+ else if (entry.getEvent() == FileSystemJournalEntry.FILE_RENAMED) {
+ if (path != null && path.equals(newFilePath))
+ {
+ // add file path to the capture queue
+ queue.add("file://" + path);
+
+ // get ready for next file
+ newFilePath = null;
+ break;
+ }
+ }
+ }
+
+ // remember the file journal change number,
+ // so we don't search the same events again and again
+ lastUSN = USN;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/media/VideoCaptureOperation.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/media/VideoCaptureOperation.java b/framework/ext/src/org/apache/cordova/media/VideoCaptureOperation.java
new file mode 100644
index 0000000..e671585
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/media/VideoCaptureOperation.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.media;
+
+import java.io.IOException;
+
+import javax.microedition.io.Connector;
+import javax.microedition.io.file.FileConnection;
+
+import org.apache.cordova.file.File;
+import org.apache.cordova.file.FileUtils;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.io.MIMETypeAssociations;
+import net.rim.device.api.ui.UiApplication;
+
+public class VideoCaptureOperation extends CaptureOperation {
+
+ // content type
+ public static String CONTENT_TYPE = "video/";
+
+ // file system listener
+ private VideoCaptureListener listener = null;
+
+ /**
+ * Creates and starts an image capture operation.
+ *
+ * @param limit
+ * maximum number of media files to capture
+ * @param callbackId
+ * the callback to receive the files
+ * @param queue
+ * the queue from which to retrieve captured media files
+ */
+ public VideoCaptureOperation(int limit, String callbackId, MediaQueue queue) {
+ super(limit, callbackId, queue);
+
+ // listener to capture image files added to file system
+ this.listener = new VideoCaptureListener(queue);
+
+ start();
+ }
+
+ /**
+ * Registers file system listener and launches native video recorder
+ * application.
+ */
+ protected void setup() {
+ // register listener for files being written
+ synchronized(UiApplication.getEventLock()) {
+ UiApplication.getUiApplication().addFileSystemJournalListener(listener);
+ }
+
+ // launch the native video recorder application
+ CameraControl.launchVideoRecorder();
+ }
+
+ /**
+ * Unregisters file system listener and closes native video recorder
+ * application.
+ */
+ protected void teardown() {
+ // remove file system listener
+ synchronized(UiApplication.getEventLock()) {
+ UiApplication.getUiApplication().removeFileSystemJournalListener(listener);
+ }
+
+ // close the native video recorder application
+ CameraControl.closeVideoRecorder();
+ }
+
+ /**
+ * Retrieves the file properties for the captured video recording.
+ *
+ * @param filePath
+ * full path of the video recording file
+ */
+ protected void processFile(String filePath) {
+ Logger.log(this.getClass().getName() + ": processing file: " + filePath);
+
+ File file = new File(FileUtils.stripSeparator(filePath));
+
+ // grab file properties
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection) Connector.open(filePath, Connector.READ);
+ if (fconn.exists()) {
+ long size = fconn.fileSize();
+ Logger.log(this.getClass().getName() + ": " + filePath + " size="
+ + Long.toString(size) + " bytes");
+ file.setLastModifiedDate(fconn.lastModified());
+ file.setName(FileUtils.stripSeparator(fconn.getName()));
+ file.setSize(size);
+ file.setType(MIMETypeAssociations.getMIMEType(filePath));
+ }
+ }
+ catch (IOException e) {
+ Logger.log(this.getClass().getName() + ": " + e);
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ } catch (IOException ignored) {}
+ }
+
+ addCaptureFile(file);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/network/ConnectionInfoAction.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/network/ConnectionInfoAction.java b/framework/ext/src/org/apache/cordova/network/ConnectionInfoAction.java
new file mode 100644
index 0000000..55a5e78
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/network/ConnectionInfoAction.java
@@ -0,0 +1,387 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.network;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.system.Application;
+import net.rim.device.api.system.CoverageInfo;
+import net.rim.device.api.system.CoverageStatusListener;
+import net.rim.device.api.system.RadioInfo;
+import net.rim.device.api.system.RadioStatusListener;
+import net.rim.device.api.system.WLANConnectionListener;
+import net.rim.device.api.system.WLANInfo;
+
+/**
+ * Determines the current network data connection type and listens for changes
+ * to network coverage. This class is intended for use through the Network
+ * plug-in.
+ */
+public class ConnectionInfoAction {
+ // Returned JSON attributes
+ private static final String EVENT = "event";
+ private static final String TYPE = "type";
+
+ // Return types
+ private static final String TYPE_UNKNOWN = "unknown";
+ private static final String TYPE_ETHERNET = "ethernet";
+ private static final String TYPE_WIFI = "wifi";
+ private static final String TYPE_2G = "2g";
+ private static final String TYPE_3G = "3g";
+ private static final String TYPE_4G = "4g";
+ private static final String TYPE_NONE = "none";
+
+ // Network status event values
+ private static final String OFFLINE = "offline";
+ private static final String ONLINE = "online";
+
+ // Network service support constants
+ private static final int DATA = RadioInfo.NETWORK_SERVICE_DATA;
+ private static final int TWO_G = RadioInfo.NETWORK_SERVICE_DATA
+ | RadioInfo.NETWORK_SERVICE_EDGE;
+ private static final int THREE_G = RadioInfo.NETWORK_SERVICE_EVDO_REV0
+ | RadioInfo.NETWORK_SERVICE_EVDO_REV0_ONLY
+ | RadioInfo.NETWORK_SERVICE_EVDO_REVA
+ | RadioInfo.NETWORK_SERVICE_EVDO_REVA_ONLY
+ | RadioInfo.NETWORK_SERVICE_UMTS;
+
+ // Listeners used to detect network changes
+ private RadioStatusListener radioListener = null;
+ private WLANConnectionListener wlanListener = null;
+ private CoverageStatusListener coverageListener = null;
+
+ // Variable indicating whether the user has disabled mobile data
+ private Boolean dataDisabled = Boolean.FALSE;
+
+ // The set of call back IDs to send results to. Using Hashtable because
+ // BlackBerry does not support Collections. There should only ever be one
+ // call back ID, but this allows multiple.
+ private Hashtable callbackIds = new Hashtable();
+
+ private String prevType = TYPE_NONE;
+ private String prevEvent = OFFLINE;
+
+ /**
+ * Determines the current network data connection type. Listeners are
+ * registered to return additional results when network state changes.
+ *
+ * @param callbackId
+ * The success call back ID to receive network type results.
+ * @return A PluginResult object with the success or failure result of the
+ * operation. A success result includes information about the
+ * currently active network type.
+ */
+ protected PluginResult getConnectionInfo(String callbackId) {
+
+ // Ensure that the dataDisabled variable is initialized.
+ setDataDisabled(CoverageInfo.getCoverageStatus(), false);
+
+ // Add the network change listeners if they have not been added.
+ addListeners(callbackId);
+
+ // Retrieve the current active connection type and build the return
+ // result.
+ String type = getConnectionType(true, true);
+ JSONObject connectInfo = new JSONObject();
+ try {
+ connectInfo.put(TYPE, type);
+ } catch (JSONException e) {
+ Logger.error("JSONException: " + e.getMessage());
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ "JSONException: " + e.getMessage());
+ }
+
+ PluginResult result = new PluginResult(PluginResult.Status.OK,
+ connectInfo);
+
+ // Need to keep the call back since listeners have been registered to
+ // fire events on the specified call back ID.
+ result.setKeepCallback(true);
+
+ return result;
+ }
+
+ /**
+ * Removes all coverage listeners and clears the list of call back IDs. This
+ * method should be invoked when the Network plug-in's onDestroy is called.
+ */
+ protected synchronized void shutdown() {
+ if (radioListener != null) {
+ Application.getApplication().removeRadioListener(radioListener);
+ radioListener = null;
+ }
+
+ if (wlanListener != null) {
+ WLANInfo.removeListener(wlanListener);
+ wlanListener = null;
+ }
+
+ if (coverageListener != null) {
+ CoverageInfo.removeListener(coverageListener);
+ coverageListener = null;
+ }
+
+ callbackIds.clear();
+ }
+
+ /**
+ * Adds a RadioStatusListener, WLANConnectionListener and
+ * CoverageStatusListener to listen for various network change events. The
+ * listeners are only registered if they have not already been added. Each
+ * listener is used to detect different types of network change events.
+ *
+ * RadioStatusListener - Detects changes in cellular data coverage.
+ * WLANConnectionListener - Detects changes in wifi coverage.
+ * CoverageStatusListener - Used to detect changes in the mobile data config
+ *
+ * @param callbackId
+ * The reference point to call back when a listener event occurs.
+ */
+ private synchronized void addListeners(String callbackId) {
+ callbackIds.put(callbackId, callbackId);
+
+ if (radioListener == null) {
+ radioListener = new RadioStatusListener() {
+ public void baseStationChange() {}
+ public void networkScanComplete(boolean success) {}
+
+ public void networkServiceChange(int networkId, int service) {
+ // Cellular data change detected. If the user hasn't
+ // disabled mobile data and wifi is not currently in use
+ // return a result indicating the cellular data coverage
+ // change.
+ if (dataDisabled == Boolean.FALSE
+ && WLANInfo.getWLANState() != WLANInfo.WLAN_STATE_CONNECTED) {
+ if ((service & DATA) == 0) {
+ sendResult(TYPE_NONE, OFFLINE);
+ } else {
+ // In the case where cell data and wifi was turned
+ // off and then the user disabled mobile data
+ // configuration, the mobile data config disablement
+ // by the user isn't detected by the coverage status
+ // listener so dataDisabled may not be accurate.
+ // When service data is activated, have to make sure
+ // that dataDisabled is properly set.
+ setDataDisabled(CoverageInfo.getCoverageStatus(),
+ false);
+ if (dataDisabled == Boolean.FALSE) {
+ sendResult(getConnectionType(false, true),
+ ONLINE);
+ }
+ }
+ }
+ }
+
+ public void networkStarted(int networkId, int service) {}
+ public void networkStateChange(int state) {}
+ public void pdpStateChange(int apn, int state, int cause) {}
+ public void radioTurnedOff() {}
+ public void signalLevel(int level) {}
+ };
+ Application.getApplication().addRadioListener(radioListener);
+ }
+
+ if (wlanListener == null) {
+ wlanListener = new WLANConnectionListener() {
+ public void networkConnected() {
+ if (dataDisabled == Boolean.FALSE) {
+ sendResult(TYPE_WIFI, ONLINE);
+ }
+ }
+
+ public void networkDisconnected(int reason) {
+ // Wifi was disconnected, if the user hasn't disabled mobile
+ // data, check if cellular data coverage exists.
+ if (dataDisabled == Boolean.FALSE) {
+ String type = getConnectionType(false, true);
+ String event = OFFLINE;
+ if (!TYPE_NONE.equals(type)) {
+ event = ONLINE;
+ }
+ sendResult(type, event);
+ }
+ }
+ };
+ WLANInfo.addListener(wlanListener);
+ }
+
+ if (coverageListener == null) {
+ coverageListener = new CoverageStatusListener() {
+ public void coverageStatusChanged(int newCoverage) {
+ // When coverage changes, check to determine if it is due
+ // to the user disabling mobile data through configuration
+ // flag.
+ setDataDisabled(newCoverage, true);
+ }
+ };
+ CoverageInfo.addListener(coverageListener);
+ }
+ }
+
+ /**
+ * Determine the type of connection currently being used for data
+ * transmission on the device. If the user has disabled mobile data then
+ * TYPE_NONE is returned regardless of cellular or wifi radio state as this
+ * is the way the browser behaves. Otherwise, wifi and/or cellular radios
+ * are queried for state based on the passed in parameters.
+ *
+ * @param checkWLAN
+ * Determines whether wifi radio state is queried or not.
+ * @param checkCell
+ * Determines whether cellular radio state is queried or not.
+ * @return A string indicating one of the defined network connections types.
+ */
+ private String getConnectionType(boolean checkWLAN, boolean checkCell) {
+ String networkType = TYPE_NONE;
+
+ if (dataDisabled == Boolean.FALSE) {
+ // Detect if wifi is active and connected. If wifi is active it
+ // takes precedence over cellular data transmission.
+ if (checkWLAN
+ && WLANInfo.getWLANState() == WLANInfo.WLAN_STATE_CONNECTED) {
+ networkType = TYPE_WIFI;
+ }
+
+ if (checkCell && TYPE_NONE.equals(networkType)
+ && RadioInfo.isDataServiceOperational()) {
+
+ int activeServices = RadioInfo.getNetworkService();
+ networkType = TYPE_UNKNOWN;
+ if ((activeServices & THREE_G) != 0) {
+ networkType = TYPE_3G;
+ } else if ((activeServices & TWO_G) != 0) {
+ networkType = TYPE_2G;
+ }
+ }
+ }
+
+ return networkType;
+ }
+
+ /**
+ * Helper function to build and send the PluginResult to the saved call back
+ * IDs.
+ *
+ * @param type
+ * The network connection type. This value should be null if the
+ * specified event is "offline".
+ * @param event
+ * The network event.
+ */
+ private void sendResult(String type, String event) {
+
+ JSONObject connectionInfo = new JSONObject();
+ try {
+ connectionInfo.put(TYPE, type);
+ connectionInfo.put(EVENT, event);
+ } catch (JSONException e) {
+ Logger.error("JSONException: " + e.getMessage());
+ for (Enumeration callbacks = this.callbackIds.elements(); callbacks
+ .hasMoreElements();) {
+ String callbackId = (String) callbacks.nextElement();
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ "JSONException: " + e.getMessage()));
+ }
+ return;
+ }
+
+ // Only send the event if it is different then the last sent event.
+ synchronized (prevType) {
+ if (prevType != null && prevEvent != null && prevType.equals(type)
+ && prevEvent.equals(event)) {
+ return;
+ } else {
+ prevType = type;
+ prevEvent = event;
+ }
+ }
+
+ PluginResult result = new PluginResult(PluginResult.Status.OK,
+ connectionInfo);
+
+ // Must keep the call back active for future events.
+ result.setKeepCallback(true);
+
+ // Iterate through the saved call back IDs. Really should only ever be
+ // one.
+ for (Enumeration callbacks = this.callbackIds.elements(); callbacks
+ .hasMoreElements();) {
+ String callbackId = (String) callbacks.nextElement();
+ CordovaExtension.invokeSuccessCallback(callbackId, result);
+ }
+
+ }
+
+ /**
+ * Determines if the user has disabled mobile data through the user level
+ * configuration panels and optionally returns an "online" or "offline"
+ * result.
+ *
+ * @param newCoverage
+ * A bit mask of CoverageInfo.COVERAGE_* flags indicating the
+ * current coverage.
+ * @param returnResult
+ * If true, return a result based on the value of the mobile data
+ * configuration.
+ */
+ private void setDataDisabled(int newCoverage, boolean returnResult) {
+
+ boolean isRadioData = (RadioInfo.getNetworkService() & DATA) != 0;
+ boolean wlanConnected = WLANInfo.getWLANState() == WLANInfo.WLAN_STATE_CONNECTED;
+ boolean eventDetected = false;
+ String event = OFFLINE;
+
+ // Note: To detect that mobile data has been disabled through
+ // configuration, determine if the current coverage is
+ // CoverageInfo.COVERAGE_NONE AND that either cellular or wifi radios
+ // are currently connected. This is because the low level radio routines
+ // will return a connected state even when mobile data is disabled
+ // through configuration.
+ synchronized (dataDisabled) {
+ if (newCoverage == CoverageInfo.COVERAGE_NONE
+ && (isRadioData || wlanConnected)) {
+ if (dataDisabled == Boolean.FALSE) {
+ Logger.log("Mobile data was disabled by the user through configuration.");
+ dataDisabled = Boolean.TRUE;
+ eventDetected = true;
+ }
+ } else if (dataDisabled == Boolean.TRUE) {
+ Logger.log("Mobile data was enabled by the user.");
+ dataDisabled = Boolean.FALSE;
+ event = ONLINE;
+ eventDetected = true;
+ }
+ }
+
+ if (returnResult && eventDetected) {
+ // The user has enabled/disabled mobile data. Return a result
+ // indicating the current network state.
+ String type = getConnectionType(true, true);
+ sendResult(type, event);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/network/Network.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/network/Network.java b/framework/ext/src/org/apache/cordova/network/Network.java
new file mode 100644
index 0000000..86e5ac6
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/network/Network.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.network;
+
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+
+/**
+ * The Network command interface.
+ *
+ * The Network class can invoke the following actions:
+ *
+ * - getConnectionInfo(callback)
+ *
+ */
+public class Network extends Plugin {
+ // Supported actions
+ public static final String ACTION_CONNECTION_INFO = "getConnectionInfo";
+
+ private ConnectionInfoAction connectionInfo = new ConnectionInfoAction();
+
+ /**
+ * Executes the request and returns CommandResult.
+ *
+ * @param action The command to execute.
+ * @param callbackId The callback ID to be invoked upon action completion
+ * @param args JSONArry of arguments for the command.
+ * @return A CommandResult object with a status and message.
+ */
+ public PluginResult execute(String action, JSONArray args, String callbackId) {
+ PluginResult result = null;
+
+ if (action.equals(ACTION_CONNECTION_INFO)) {
+ result = connectionInfo.getConnectionInfo(callbackId);
+ }
+ else {
+ result = new PluginResult(PluginResult.Status.INVALID_ACTION, "Network: Invalid action: " + action);
+ }
+
+ return result;
+ }
+
+ /**
+ * Called when Plugin is destroyed.
+ */
+ public void onDestroy() {
+ connectionInfo.shutdown();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/notification/ActivityDialog.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/notification/ActivityDialog.java b/framework/ext/src/org/apache/cordova/notification/ActivityDialog.java
new file mode 100644
index 0000000..710f61e
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/notification/ActivityDialog.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.notification;
+
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.ui.SpacerField;
+
+import net.rim.device.api.system.Bitmap;
+import net.rim.device.api.system.Characters;
+import net.rim.device.api.ui.UiApplication;
+import net.rim.device.api.ui.component.BitmapField;
+import net.rim.device.api.ui.component.LabelField;
+import net.rim.device.api.ui.component.SeparatorField;
+import net.rim.device.api.ui.container.HorizontalFieldManager;
+import net.rim.device.api.ui.container.PopupScreen;
+import net.rim.device.api.ui.container.VerticalFieldManager;
+
+/**
+ * A Popup activity dialog box with an optional title and message.
+ */
+public final class ActivityDialog extends PopupScreen {
+ private static ActivityDialog dialog = null;
+
+ /**
+ * Construct an activity dialog, with customizable title and message.
+ *
+ * @param title
+ * Title of the activity dialog
+ * @param message
+ * Message to print in the body of the dialog
+ */
+ private ActivityDialog(String title, String message) {
+ super(new VerticalFieldManager());
+
+ if (title != null && title.length() > 0) {
+ add(new LabelField(title));
+ add(new SeparatorField(SeparatorField.LINE_HORIZONTAL));
+ }
+ add(new SpacerField(0, 20));
+
+ HorizontalFieldManager hfm = new HorizontalFieldManager();
+
+ Bitmap hourglass = Bitmap.getPredefinedBitmap(Bitmap.HOURGLASS);
+
+ BitmapField bitmap = new BitmapField(hourglass);
+ hfm.add(bitmap);
+
+ if (message != null && message.length() > 0) {
+ hfm.add(new LabelField(message, FIELD_HCENTER | FIELD_VCENTER));
+ }
+
+ add(hfm);
+ add(new SpacerField(0, 20));
+ }
+
+ /**
+ * Creates and displays the activity dialog.
+ *
+ * @param args
+ * JSONArray of arguments.
+ * @return a PluginResult indicating success or error.
+ */
+ static synchronized PluginResult start(JSONArray args) {
+ if (dialog == null) {
+ String message = null;
+ String title = null;
+
+ // Title and message are optional, grab the strings from the args
+ // if they are there.
+ if (args != null && args.length() > 0) {
+ try {
+ if (!args.isNull(0)) {
+ title = args.getString(0);
+ }
+ if (args.length() > 1 && !args.isNull(1)) {
+ message = args.getString(1);
+ }
+ } catch (JSONException e) {
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ "JSONException: " + e.getMessage());
+ }
+ }
+
+ dialog = new ActivityDialog(title, message);
+ final UiApplication uiApp = UiApplication.getUiApplication();
+ uiApp.invokeLater(new Runnable() {
+ public void run() {
+ uiApp.pushModalScreen(dialog);
+ }
+ });
+ }
+
+ return new PluginResult(PluginResult.Status.OK, "");
+ }
+
+ /**
+ * Closes the activity dialog.
+ *
+ * @return a PluginResult indicating success or error.
+ */
+ static synchronized PluginResult stop() {
+ if (dialog != null) {
+ final UiApplication uiApp = UiApplication.getUiApplication();
+ final ActivityDialog tmpDialog = dialog;
+ uiApp.invokeLater(new Runnable() {
+ public void run() {
+ uiApp.popScreen(tmpDialog);
+ }
+ });
+ dialog = null;
+ }
+
+ return new PluginResult(PluginResult.Status.OK, "");
+ }
+
+ /**
+ * @see net.rim.device.api.ui.Screen#keyChar(char, int, int)
+ */
+ protected boolean keyChar(char key, int status, int time) {
+ // If the user clicks back key while progress dialog is displayed, close
+ // the progress dialog.
+ if (key == Characters.ESCAPE) {
+ stop();
+ }
+
+ return super.keyChar(key, status, time);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/notification/AlertAction.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/notification/AlertAction.java b/framework/ext/src/org/apache/cordova/notification/AlertAction.java
new file mode 100644
index 0000000..c04a3cb
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/notification/AlertAction.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.notification;
+
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+
+import net.rim.device.api.ui.UiApplication;
+
+/**
+ * Alert Action
+ *
+ * Displays and interacts with a dialog box.
+ *
+ */
+public class AlertAction {
+
+ private static final String DEFAULT_MESSAGE = "";
+ private static final String DEFAULT_TITLE = "Alert";
+ private static final String DEFAULT_BUTTON = "OK";
+
+ /**
+ * Displays a custom alert.
+ *
+ * @param args JSONArray formatted as [ message, title, buttonLabel ]
+ * message: the message to display in the dialog body (default: "").
+ * title: the title to display at the top of the dialog (default: "Alert").
+ * buttonLabel: the button text (default: "OK").
+ * @return A PluginResult object with the success or failure state for displaying the dialog box.
+ */
+ public static PluginResult execute(JSONArray args) {
+
+ PluginResult result = null;
+
+ try {
+ String message = DEFAULT_MESSAGE;
+ String title = DEFAULT_TITLE;
+ String buttonLabel = DEFAULT_BUTTON;
+ if (args.length() > 0 && !args.isNull(0))
+ message = args.getString(0);
+ if (args.length() > 1 && !args.isNull(1))
+ title = args.getString(1);
+ if (args.length() > 2 && !args.isNull(2))
+ buttonLabel = args.getString(2);
+
+ // construct the dialog
+ final AlertDialog dialog = new AlertDialog(message, title, buttonLabel);
+
+ // ask the event dispatch thread to show dialog
+ Runnable runnable = new Runnable() {
+ public void run() {
+ UiApplication ui = UiApplication.getUiApplication();
+ ui.pushModalScreen(dialog);
+ }
+ };
+ UiApplication.getUiApplication().invokeAndWait(runnable);
+
+ result = new PluginResult(PluginResult.Status.OK);
+ }
+ catch (JSONException e) {
+ result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, "JSONException: " + e.getMessage());
+ }
+
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/notification/AlertDialog.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/notification/AlertDialog.java b/framework/ext/src/org/apache/cordova/notification/AlertDialog.java
new file mode 100644
index 0000000..a32666b
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/notification/AlertDialog.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.notification;
+
+import org.apache.cordova.ui.SpacerField;
+
+import net.rim.device.api.ui.Field;
+import net.rim.device.api.ui.FieldChangeListener;
+import net.rim.device.api.ui.component.ButtonField;
+import net.rim.device.api.ui.component.LabelField;
+import net.rim.device.api.ui.component.SeparatorField;
+import net.rim.device.api.ui.container.PopupScreen;
+import net.rim.device.api.ui.container.VerticalFieldManager;
+
+public final class AlertDialog extends PopupScreen implements FieldChangeListener {
+
+ private ButtonField button;
+
+ /**
+ * Open a custom alert dialog, with a customizable title and button text.
+ *
+ * @param {String} message Message to print in the body of the alert
+ * @param {String} title Title of the alert dialog (default: 'Alert')
+ * @param {String} buttonLabel Label of the close button (default: 'OK')
+ */
+ public AlertDialog(String message, String title, String buttonLabel) {
+
+ super(new VerticalFieldManager());
+
+ // title
+ add(new LabelField(title));
+
+ // separator
+ add(new SeparatorField(SeparatorField.LINE_HORIZONTAL));
+
+ // message
+ add(new SpacerField(0, 20));
+ add(new LabelField(message, FIELD_HCENTER | FIELD_VCENTER));
+ add(new SpacerField(0, 20));
+
+ // button
+ button = new ButtonField(buttonLabel, ButtonField.CONSUME_CLICK | FIELD_HCENTER);
+ button.setChangeListener(this);
+ add(button);
+ }
+
+ public void fieldChanged(Field field, int context) {
+ if (button == field) {
+ close();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/notification/BeepAction.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/notification/BeepAction.java b/framework/ext/src/org/apache/cordova/notification/BeepAction.java
new file mode 100644
index 0000000..b1c3658
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/notification/BeepAction.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.notification;
+
+import java.io.IOException;
+
+import javax.microedition.media.Control;
+import javax.microedition.media.Manager;
+import javax.microedition.media.MediaException;
+import javax.microedition.media.Player;
+import javax.microedition.media.control.ToneControl;
+import javax.microedition.media.control.VolumeControl;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.media.control.AudioPathControl;
+import net.rim.device.api.notification.NotificationsManager;
+import net.rim.device.api.system.Alert;
+
+/**
+ * Beep Action plays a device tone.
+ */
+public class BeepAction {
+
+ private static final int BEEP_VOLUME = 100;
+ private static final byte NOTE = 76; // E5
+ private static final byte D = 16; // quarter note
+ private static final byte tempo = 30; // 120 bpm
+
+ private static final byte[] TONE_SEQUENCE_START = {
+ ToneControl.VERSION, 1,
+ ToneControl.TEMPO, tempo
+ };
+
+ private static final byte[] TONE = {
+ NOTE,D, ToneControl.SILENCE,D/2
+ };
+
+ /**
+ * Beeps the device for a given number of times. By default, it will beep
+ * once. If the user explicitly sets the beep count to zero, it will play
+ * the applications notification profile. The application profile playback
+ * sequence is controlled by the user.
+ *
+ * @param args JSONArray formatted as [ count ]
+ * count: specifies the number of times to beep the device (default: 1).
+ * @return A CommandResult object with the success or failure
+ * state for beeping the device.
+ */
+ public static PluginResult execute(JSONArray args) {
+ PluginResult result = null;
+
+ if (Alert.isAudioSupported()) {
+
+ try {
+ int repeatCount = 1;
+ if (args.length() > 0 && !args.isNull(0)) {
+ repeatCount = args.getInt(0);
+ }
+
+ // play tone n times
+ if (repeatCount > 0) {
+ playTone(repeatCount);
+ }
+ // FIXME: unsupported on other platforms
+ // send notification event to application profile
+ else {
+ NotificationsManager.triggerImmediateEvent(
+ CordovaExtension.getAppID(), 0, null, null);
+ }
+ }
+ catch (JSONException e) {
+ Logger.log(BeepAction.class.getName() + ": " + e);
+ result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
+ }
+ catch (Exception e) {
+ Logger.log(BeepAction.class.getName() + ": " + e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, e.getMessage());
+ }
+
+ result = new PluginResult(PluginResult.Status.OK);
+ }
+ else {
+ result = new PluginResult(PluginResult.Status.ILLEGAL_ACCESS_EXCEPTION, "Audio not supported");
+ }
+
+ return result;
+ }
+
+ /**
+ * Plays a tone the specified number of times on the device audio system.
+ * @param repeatCount number of times to play tone
+ */
+ private static void playTone(int repeatCount) throws MediaException, IOException {
+
+ // get tone player
+ Player p = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR);
+ p.realize();
+
+ // set tone sequence
+ ToneControl tc = (ToneControl)p.getControl("ToneControl");
+ tc.setSequence(getToneSequence(repeatCount));
+
+ // crank up the volume
+ VolumeControl vc = (VolumeControl)p.getControl("VolumeControl");
+ vc.setLevel(BEEP_VOLUME);
+
+ // route audio to speaker phone
+ p.prefetch();
+ Control[] c = p.getControls();
+ for(int i=c.length-1; i>=0; --i) {
+ if(c[i] instanceof AudioPathControl) {
+ AudioPathControl apc = (AudioPathControl)c[i];
+ apc.setAudioPath(AudioPathControl.AUDIO_PATH_HANDSFREE);
+ break;
+ }
+ }
+
+ // play
+ p.start();
+ }
+
+ /**
+ * Creates a tone sequence to play.
+ * @param repeatCount number of times to play tone
+ * @return tone sequence
+ */
+ private static byte[] getToneSequence(int repeatCount) {
+ // we have to build the sequence dynamically because
+ // ToneControl.REPEAT, repeatCount, TONE, DURATION
+ // doesn't seem to work
+ byte[] sequence = new byte[TONE_SEQUENCE_START.length + TONE.length * repeatCount];
+ System.arraycopy(TONE_SEQUENCE_START, 0, sequence, 0, TONE_SEQUENCE_START.length);
+ for (int i = 0; i < repeatCount; i++) {
+ System.arraycopy(TONE, 0, sequence, (TONE_SEQUENCE_START.length+TONE.length*i), TONE.length);
+ }
+
+ return sequence;
+ }
+}