From 03f6267c8252ef70f3d3fb83807bfa94e4f02e2e Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Wed, 15 Sep 2010 14:06:05 -0500 Subject: [PATCH 01/20] Add JSON stringify equivalent not implemented in older Android (1.6) devices. This is needed for args passed to PhoneGap.exec(). --- framework/assets/js/phonegap.js.base | 36 +++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index 49640d5d..b3a09655 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -219,6 +219,36 @@ document.addEventListener = function(evt, handler, capture) { } }; +/** + * If JSON not included, use our own stringify. (Android 1.6) + * The restriction on ours is that it must be an array of simple types. + * + * @param args + * @return + */ +PhoneGap.stringify = function(args) { + if (typeof JSON == "undefined") { + var s = "["; + for (var i=0; i 0) { + s = s + ","; + } + var type = typeof args[i]; + if ((type == "number") || (type == "boolean")) { + s = s + args[i]; + } + else { + s = s + '"' + args[i] + '"'; + } + } + s = s + "]"; + return s; + } + else { + return JSON.stringify(args); + } +}; + PhoneGap.callbackId = 0; PhoneGap.callbacks = {}; @@ -232,7 +262,7 @@ PhoneGap.callbacks = {}; PhoneGap.exec = function(clazz, action, args) { try { var callbackId = 0; - var r = PluginManager.exec(clazz, action, callbackId, JSON.stringify(args), false); + var r = PluginManager.exec(clazz, action, callbackId, this.stringify(args), false); eval("var v="+r+";"); // If status is OK, then return value back to caller @@ -256,10 +286,10 @@ PhoneGap.execAsync = function(success, fail, clazz, action, args) { if (success || fail) { PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; } - var r = PluginManager.exec(clazz, action, callbackId, JSON.stringify(args), true); + var r = PluginManager.exec(clazz, action, callbackId, this.stringify(args), true); // If a result was returned - if (r) { + if (typeof r == "string") { eval("var v="+r+";"); // If status is OK, then return value back to caller From c050e00b8fda9559e9b7eaecbcba671414f68e87 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Wed, 15 Sep 2010 14:17:40 -0500 Subject: [PATCH 02/20] Use timeout to break out of possible infinite loop waiting for sensor to start. --- framework/src/com/phonegap/AccelListener.java | 7 ++++++- framework/src/com/phonegap/CompassListener.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/framework/src/com/phonegap/AccelListener.java b/framework/src/com/phonegap/AccelListener.java index 8896f018..4f4cae62 100755 --- a/framework/src/com/phonegap/AccelListener.java +++ b/framework/src/com/phonegap/AccelListener.java @@ -105,13 +105,18 @@ public class AccelListener implements SensorEventListener, Plugin{ return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START); } // Wait until running - while (this.status == STARTING) { + long timeout = 2000; + while ((this.status == STARTING) && (timeout > 0)) { + timeout = timeout - 100; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } + if (timeout == 0) { + return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START); + } } JSONObject r = new JSONObject(); r.put("x", this.x); diff --git a/framework/src/com/phonegap/CompassListener.java b/framework/src/com/phonegap/CompassListener.java index 2f3bf549..6ff6efe1 100755 --- a/framework/src/com/phonegap/CompassListener.java +++ b/framework/src/com/phonegap/CompassListener.java @@ -98,13 +98,18 @@ public class CompassListener implements SensorEventListener, Plugin{ return new PluginResult(PluginResult.Status.IO_EXCEPTION, ERROR_FAILED_TO_START); } // Wait until running - while (this.status == STARTING) { + long timeout = 2000; + while ((this.status == STARTING) && (timeout > 0)) { + timeout = timeout - 100; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } + if (timeout == 0) { + return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START); + } } float f = this.getHeading(); return new PluginResult(status, f); From 705b8f687488b4a73f3f5f5e4b518760a52c97e4 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Wed, 15 Sep 2010 14:27:46 -0500 Subject: [PATCH 03/20] Change Device JS object to include only platform, uuid, version, and phonegap properties as defined in API, and modify Device Java class to implement plugin interface. --- framework/assets/js/device.js | 69 +++---- framework/assets/js/notification.js | 27 +-- framework/src/com/phonegap/Device.java | 234 ++++++++++++++++++----- framework/src/com/phonegap/DroidGap.java | 4 +- 4 files changed, 231 insertions(+), 103 deletions(-) diff --git a/framework/assets/js/device.js b/framework/assets/js/device.js index 17bd9bb8..450a0e32 100644 --- a/framework/assets/js/device.js +++ b/framework/assets/js/device.js @@ -1,59 +1,60 @@ /** - * this represents the mobile device, and provides properties for inspecting the model, version, UUID of the + * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the * phone, etc. * @constructor */ function Device() { this.available = PhoneGap.available; this.platform = null; - this.version = null; - this.name = null; - this.gap = null; - this.uuid = null; - try { - if (window.DroidGap) { - this.available = true; - this.uuid = window.DroidGap.getUuid(); - this.version = window.DroidGap.getOSVersion(); - this.gapVersion = window.DroidGap.getVersion(); - this.platform = window.DroidGap.getPlatform(); - this.name = window.DroidGap.getProductName(); - this.line1Number = window.DroidGap.getLine1Number(); - this.deviceId = window.DroidGap.getDeviceId(); - this.simSerialNumber = window.DroidGap.getSimSerialNumber(); - this.subscriberId = window.DroidGap.getSubscriberId(); - } - } catch(e) { - this.available = false; - } + this.version = null; + this.name = null; + this.uuid = null; + this.phonegap = null; + + var me = this; + PhoneGap.execAsync( + function(info) { + me.available = true; + me.platform = info.platform; + me.version = info.version; + me.uuid = info.uuid; + me.phonegap = info.phonegap; + }, + function(e) { + me.available = false; + console.log("Error initializing PhoneGap: " + e); + alert("Error initializing PhoneGap: "+e); + }, + "Device", "getDeviceInfo", []); } /* - * You must explicitly override the back button. + * This is only for Android. + * + * You must explicitly override the back button. */ - -Device.prototype.overrideBackButton = function() -{ - BackButton.override(); +Device.prototype.overrideBackButton = function() { + BackButton.override(); } /* + * This is only for Android. + * * This resets the back button to the default behaviour */ - -Device.prototype.resetBackButton = function() -{ - BackButton.reset(); +Device.prototype.resetBackButton = function() { + BackButton.reset(); } /* + * This is only for Android. + * * This terminates the activity! */ -Device.prototype.exitApp = function() -{ - BackButton.exitApp(); +Device.prototype.exitApp = function() { + BackButton.exitApp(); } PhoneGap.addConstructor(function() { navigator.device = window.device = new Device(); -}); \ No newline at end of file +}); diff --git a/framework/assets/js/notification.js b/framework/assets/js/notification.js index 5a7c733b..e80c3e40 100644 --- a/framework/assets/js/notification.js +++ b/framework/assets/js/notification.js @@ -2,7 +2,6 @@ * This class provides access to notifications on the device. */ function Notification() { - } /** @@ -34,7 +33,7 @@ Notification.prototype.activityStop = function() { * @param {String} colour The colour of the light. */ Notification.prototype.blink = function(count, colour) { - + }; /** @@ -42,16 +41,17 @@ Notification.prototype.blink = function(count, colour) { * @param {Integer} mills The number of milliseconds to vibrate for. */ Notification.prototype.vibrate = function(mills) { - + PhoneGap.execAsync(null, null, "Device", "vibrate", [mills]); }; /** * Causes the device to beep. + * On Android, the default notification ringtone is played. + * * @param {Integer} count The number of beeps. - * @param {Integer} volume The volume of the beep. */ -Notification.prototype.beep = function(count, volume) { - +Notification.prototype.beep = function(count) { + PhoneGap.execAsync(null, null, "Device", "beep", [count]); }; // TODO: of course on Blackberry and Android there notifications in the UI as well @@ -60,18 +60,3 @@ PhoneGap.addConstructor(function() { if (typeof navigator.notification == "undefined") navigator.notification = new Notification(); }); -Notification.prototype.vibrate = function(mills) -{ - DroidGap.vibrate(mills); -} - -/* - * On the Android, we don't beep, we notify you with your - * notification! We shouldn't keep hammering on this, and should - * review what we want beep to do. - */ - -Notification.prototype.beep = function(count, volume) -{ - DroidGap.beep(count); -} diff --git a/framework/src/com/phonegap/Device.java b/framework/src/com/phonegap/Device.java index 922fd01f..babd09fe 100644 --- a/framework/src/com/phonegap/Device.java +++ b/framework/src/com/phonegap/Device.java @@ -24,7 +24,15 @@ package com.phonegap; import java.util.TimeZone; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.os.Vibrator; import android.provider.Settings; @@ -33,76 +41,210 @@ import android.webkit.WebView; import android.media.Ringtone; import android.media.RingtoneManager; -public class Device{ +public class Device implements Plugin { - private static final String LOG_TAG = "PhoneGap"; - /* - * UUID, version and availability - */ - public boolean droid = true; - public static String version = "0.91"; - public static String platform = "Android"; - public static String uuid; - private Context mCtx; - private WebView mAppView; - AudioPlayer audio; + public static String phonegapVersion = "pre-0.92 EDGE"; // PhoneGap version + public static String platform = "Android"; // Device OS + public static String uuid; // Device UUID + private DroidGap ctx; // DroidGap object + @SuppressWarnings("unused") + private WebView webView; // Webview object - public Device(WebView appView, Context ctx) { - this.mCtx = ctx; - this.mAppView = appView; - uuid = getUuid(); + /** + * Constructor. + */ + public Device() { } - public void beep(long pattern) - { + /** + * Sets the context of the Command. This can then be used to do things like + * get file paths associated with the Activity. + * + * @param ctx The context of the main Activity. + */ + public void setContext(DroidGap ctx) { + this.ctx = ctx; + Device.uuid = getUuid(); + } + + /** + * Sets the main View of the application, this is the WebView within which + * a PhoneGap app runs. + * + * @param webView The PhoneGap WebView + */ + public void setView(WebView webView) { + this.webView = webView; + } + + /** + * Executes the request and returns CommandResult. + * + * @param action The command to execute. + * @param args JSONArry of arguments for the command. + * @return A CommandResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + try { + if (action.equals("getDeviceInfo")) { + JSONObject r = new JSONObject(); + r.put("uuid", Device.uuid); + r.put("version", this.getOSVersion()); + r.put("platform", Device.platform); + r.put("name", this.getProductName()); + r.put("phonegap", Device.phonegapVersion); + //JSONObject pg = new JSONObject(); + //pg.put("version", Device.phonegapVersion); + //r.put("phonegap", pg); + return new PluginResult(status, r); + } + else if (action.equals("beep")) { + this.beep(args.getLong(0)); + } + else if (action.equals("vibrate")) { + this.vibrate(args.getLong(0)); + } + return new PluginResult(status, result); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + /** + * Identifies if action to be executed returns a value and should be run synchronously. + * + * @param action The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + if (action.equals("getDeviceInfo")) { + return true; + } + return false; + } + + /** + * Called when the system is about to start resuming a previous activity. + */ + public void onPause() { + } + + /** + * Called when the activity will start interacting with the user. + */ + public void onResume() { + } + + /** + * Called when the activity is to be shut down. + * Stop listener. + */ + public void onDestroy() { + } + + /** + * Called when an activity you launched exits, giving you the requestCode you started it with, + * the resultCode it returned, and any additional data from it. + * + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + */ + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + } + + //-------------------------------------------------------------------------- + // LOCAL METHODS + //-------------------------------------------------------------------------- + + /** + * Beep plays the default notification ringtone. + * + * @param count Number of times to play notification + */ + public void beep(long count) { Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - Ringtone notification = RingtoneManager.getRingtone(mCtx, ringtone); - if (notification != null) { // This will be the case when the phone is set to silent for example - for (long i = 0; i < pattern; ++i) - { + Ringtone notification = RingtoneManager.getRingtone(this.ctx, ringtone); + + // If phone is not set to silent mode + if (notification != null) { + for (long i = 0; i < count; ++i) { notification.play(); + long timeout = 5000; + while (notification.isPlaying() && (timeout > 0)) { + timeout = timeout - 100; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } } } } - public void vibrate(long pattern){ + /** + * Vibrates the device for the specified amount of time. + * + * @param time Time to vibrate in ms. + */ + public void vibrate(long time){ // Start the vibration, 0 defaults to half a second. - if (pattern == 0) - pattern = 500; - Vibrator vibrator = (Vibrator) mCtx.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(pattern); + if (time == 0) { + time = 500; + } + Vibrator vibrator = (Vibrator) this.ctx.getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(time); } - public String getPlatform() - { - return this.platform; + /** + * Get the OS name. + * + * @return + */ + public String getPlatform() { + return Device.platform; } - public String getUuid() - { - //TelephonyManager operator = (TelephonyManager) mCtx.getSystemService(Context.TELEPHONY_SERVICE); - //String uuid = operator.getDeviceId(); - String uuid = Settings.Secure.getString(mCtx.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); + /** + * Get the device's Universally Unique Identifier (UUID). + * + * @return + */ + public String getUuid() { + String uuid = Settings.Secure.getString(this.ctx.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); return uuid; } + /** + * Get the PhoneGap version. + * + * @return + */ + public String getPhonegapVersion() { + return Device.phonegapVersion; + } + public String getLine1Number(){ - TelephonyManager operator = (TelephonyManager)mCtx.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); return operator.getLine1Number(); } public String getDeviceId(){ - TelephonyManager operator = (TelephonyManager)mCtx.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); return operator.getDeviceId(); } public String getSimSerialNumber(){ - TelephonyManager operator = (TelephonyManager)mCtx.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); return operator.getSimSerialNumber(); } public String getSubscriberId(){ - TelephonyManager operator = (TelephonyManager)mCtx.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); return operator.getSubscriberId(); } @@ -116,21 +258,23 @@ public class Device{ String productname = android.os.Build.PRODUCT; return productname; } - public String getOSVersion() - { + + /** + * Get the OS version. + * + * @return + */ + public String getOSVersion() { String osversion = android.os.Build.VERSION.RELEASE; return osversion; } + public String getSDKVersion() { String sdkversion = android.os.Build.VERSION.SDK; return sdkversion; } - public String getVersion() - { - return version; - } public String getTimeZoneID() { TimeZone tz = TimeZone.getDefault(); diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 371d4d23..3fa921ed 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -83,7 +83,6 @@ public class DroidGap extends Activity { protected Boolean loadInWebView = false; private LinearLayout root; - private Device gap; private FileUtils fs; private BrowserKey mKey; public CallbackServer callbackServer; @@ -270,13 +269,11 @@ public class DroidGap extends Activity { private void bindBrowser(WebView appView) { this.callbackServer = new CallbackServer(); this.pluginManager = new PluginManager(appView, this); - this.gap = new Device(appView, this); this.fs = new FileUtils(appView, this); this.mKey = new BrowserKey(appView, this); // This creates the new javascript interfaces for PhoneGap appView.addJavascriptInterface(this.pluginManager, "PluginManager"); - appView.addJavascriptInterface(this.gap, "DroidGap"); appView.addJavascriptInterface(this.fs, "FileUtil"); appView.addJavascriptInterface(this.mKey, "BackButton"); @@ -295,6 +292,7 @@ public class DroidGap extends Activity { } + this.addService("Device", "com.phonegap.Device"); this.addService("Accelerometer", "com.phonegap.AccelListener"); this.addService("Compass", "com.phonegap.CompassListener"); this.addService("Media", "com.phonegap.AudioHandler"); From 5cd25316fa02f88c5b887234b1ec706b830aa885 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Wed, 15 Sep 2010 14:38:57 -0500 Subject: [PATCH 04/20] Change JS to call navigator.contacts instead of navigator.ContactManager, which no longer exists. --- framework/src/com/phonegap/ContactManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/com/phonegap/ContactManager.java b/framework/src/com/phonegap/ContactManager.java index 82cc5644..8924ccf9 100755 --- a/framework/src/com/phonegap/ContactManager.java +++ b/framework/src/com/phonegap/ContactManager.java @@ -256,14 +256,14 @@ public class ContactManager implements Plugin { // Code for backwards compatibility with the OLD Contacts API if (all) { - this.ctx.sendJavascript("navigator.ContactManager.droidAddContact('" + name + "','" + phoneNumber + "','" + email +"');"); + this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + name + "','" + phoneNumber + "','" + email +"');"); } else { this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + name + "','" + phoneNumber + "','" + email +"');"); } } while (cur.moveToNext()); if (all) { - this.ctx.sendJavascript("navigator.ContactManager.droidDone();"); + this.ctx.sendJavascript("navigator.contacts.droidDone();"); } else { this.ctx.sendJavascript("navigator.contacts.droidDone();"); @@ -272,7 +272,7 @@ public class ContactManager implements Plugin { else { if (all) { - this.ctx.sendJavascript("navigator.ContactManager.fail();"); + this.ctx.sendJavascript("navigator.contacts.fail('Error');"); } else { this.ctx.sendJavascript("navigator.contacts.fail('None found!');"); @@ -299,7 +299,7 @@ public class ContactManager implements Plugin { if(data != null) { data.email = email; - this.ctx.sendJavascript("navigator.Contacts.droidFoundContact('" + data.name + "','" + data.phone + "','" + data.email +"');"); + this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + data.name + "','" + data.phone + "','" + data.email +"');"); } } while (cur.moveToNext()); this.ctx.sendJavascript("navigator.contacts.droidDoneContacts();"); From 9c2e4cfd9c297b94741529911713655d64d25a77 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Thu, 16 Sep 2010 11:28:52 -0500 Subject: [PATCH 05/20] Check to make sure result was returned to eliminate parse warning messages. --- framework/assets/js/phonegap.js.base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index b3a09655..af0fb556 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -289,7 +289,7 @@ PhoneGap.execAsync = function(success, fail, clazz, action, args) { var r = PluginManager.exec(clazz, action, callbackId, this.stringify(args), true); // If a result was returned - if (typeof r == "string") { + if ((typeof r == "string") && (r.length > 0)) { eval("var v="+r+";"); // If status is OK, then return value back to caller From 92d2b5812c027b59d2e11d015f0cf850b9b18c20 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Thu, 16 Sep 2010 11:04:27 -0500 Subject: [PATCH 06/20] Change camera to be more consistent with iPhone and BB widgets. Add support to choose image from library instead of only camera. --- framework/assets/js/camera.js | 48 ++++- .../src/com/phonegap/CameraLauncher.java | 189 ++++++++++++------ framework/src/com/phonegap/DroidGap.java | 18 +- 3 files changed, 176 insertions(+), 79 deletions(-) mode change 100644 => 100755 framework/assets/js/camera.js diff --git a/framework/assets/js/camera.js b/framework/assets/js/camera.js old mode 100644 new mode 100755 index 4b42470f..e7a65ee7 --- a/framework/assets/js/camera.js +++ b/framework/assets/js/camera.js @@ -4,14 +4,44 @@ * * @constructor */ -function Camera() { +Camera = function() { this.successCallback = null; this.errorCallback = null; this.options = null; }; /** - * Takes a photo and returns the image as a base64 encoded `String`. + * Format of image that returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * pictureSourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.DestinationType = { + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) +}; + +/** + * Source to getPicture from. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * pictureSourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.PictureSourceType = { + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) +}; + +/** + * Gets a picture from source defined by "options.pictureSourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are pictureSourceType=CAMERA and destinationType=DATA_URL. * * @param {Function} successCallback * @param {Function} errorCallback @@ -34,15 +64,19 @@ Camera.prototype.getPicture = function(successCallback, errorCallback, options) this.successCallback = successCallback; this.errorCallback = errorCallback; this.options = options; - var capturetype = "base64"; var quality = 80; - if (this.options.capturetype) { - capturetype = this.options.capturetype; - } if (options.quality) { quality = this.options.quality; } - PhoneGap.execAsync(null, null, "Camera", "takePicture", [quality, capturetype]); + var destinationType = Camera.DestinationType.DATA_URL; + if (this.options.destinationType) { + destinationType = this.options.destinationType; + } + var pictureSourceType = Camera.PictureSourceType.CAMERA; + if (typeof this.options.pictureSourceType == "number") { + pictureSourceType = this.options.pictureSourceType; + } + PhoneGap.execAsync(null, null, "Camera", "takePicture", [quality, destinationType, pictureSourceType]); }; /** diff --git a/framework/src/com/phonegap/CameraLauncher.java b/framework/src/com/phonegap/CameraLauncher.java index 92183000..cb05721b 100755 --- a/framework/src/com/phonegap/CameraLauncher.java +++ b/framework/src/com/phonegap/CameraLauncher.java @@ -2,6 +2,7 @@ package com.phonegap; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -20,6 +21,7 @@ import android.graphics.Bitmap.CompressFormat; import android.net.Uri; import android.os.Environment; import android.webkit.WebView; +import android.provider.MediaStore; /** * This class launches the camera view, allows the user to take a picture, closes the camera view, @@ -28,12 +30,18 @@ import android.webkit.WebView; */ public class CameraLauncher implements Plugin { - WebView webView; // WebView object - DroidGap ctx; // DroidGap object + private static final int DATA_URL = 0; // Return base64 encoded string + private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android) + + private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + private static final int CAMERA = 1; // Take picture from camera + private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture library (same as PHOTOLIBRARY for Android) + + WebView webView; // WebView object + DroidGap ctx; // DroidGap object - private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - private Uri imageUri; // Uri of captured image - private boolean base64 = true; + private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + private Uri imageUri; // Uri of captured image /** * Constructor. @@ -74,7 +82,20 @@ public class CameraLauncher implements Plugin { try { if (action.equals("takePicture")) { - this.takePicture(args.getInt(0), args.getString(1)); + int destType = DATA_URL; + if (args.length() > 1) { + destType = args.getInt(1); + } + int srcType = CAMERA; + if (args.length() > 2) { + srcType = args.getInt(2); + } + if (srcType == CAMERA) { + this.takePicture(args.getInt(0), destType); + } + else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { + this.getImage(srcType, destType); + } } return new PluginResult(status, result); } catch (JSONException e) { @@ -130,12 +151,8 @@ public class CameraLauncher implements Plugin { * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) * @param returnType Set the type of image to return. */ - public void takePicture(int quality, String returnType) { + public void takePicture(int quality, int returnType) { this.mQuality = quality; - this.base64 = false; - if (returnType.equals("base64")) { - this.base64 = true; - } // Display camera Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); @@ -146,7 +163,22 @@ public class CameraLauncher implements Plugin { intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); this.imageUri = Uri.fromFile(photo); - this.ctx.startActivityForResult((Plugin) this, intent); + this.ctx.startActivityForResult((Plugin) this, intent, (CAMERA+1)*16 + returnType+1); + } + + /** + * Get image from photo library. + * + * @param returnType + */ + // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! + public void getImage(int srcType, int returnType) { + Intent intent = new Intent(); + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + this.ctx.startActivityForResult((Plugin) this, Intent.createChooser(intent, + new String("Get Picture")), (srcType+1)*16 + returnType + 1); } /** @@ -158,61 +190,98 @@ public class CameraLauncher implements Plugin { * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). */ public void onActivityResult(int requestCode, int resultCode, Intent intent) { + + // Get src and dest types from request code + int srcType = (requestCode/16) - 1; + int destType = (requestCode % 16) - 1; - // If image available - if (resultCode == Activity.RESULT_OK) { - try { - // Read in bitmap of captured image - Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); - + // If CAMERA + if (srcType == CAMERA) { + + // If image available + if (resultCode == Activity.RESULT_OK) { + try { + // Read in bitmap of captured image + Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + + // If sending base64 image back + if (destType == DATA_URL) { + this.processPicture(bitmap); + } + + // If sending filename back + else if (destType == FILE_URI){ + // Create entry in media store for image + // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) + ContentValues values = new ContentValues(); + values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); + Uri uri = null; + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException e) { + System.out.println("Can't write to external media storage."); + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException ex) { + System.out.println("Can't write to internal media storage."); + this.failPicture("Error capturing image - no media storage found."); + return; + } + } + + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); + os.close(); + + // Send Uri back to JavaScript for viewing image + this.ctx.sendJavascript("navigator.camera.success('" + uri.toString() + "');"); + } + } catch (IOException e) { + e.printStackTrace(); + this.failPicture("Error capturing image."); + } + } + + // If cancelled + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Camera cancelled."); + } + + // If something else + else { + this.failPicture("Did not complete!"); + } + } + + // If retrieving photo from library + else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { + if (resultCode == Activity.RESULT_OK) { + Uri uri = intent.getData(); + android.content.ContentResolver resolver = this.ctx.getContentResolver(); // If sending base64 image back - if (this.base64) { - this.processPicture(bitmap); + if (destType == DATA_URL) { + try { + Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); + this.processPicture(bitmap); + } catch (FileNotFoundException e) { + e.printStackTrace(); + this.failPicture("Error retrieving image."); + } } // If sending filename back - else { - // Create entry in media store for image - // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) - ContentValues values = new ContentValues(); - values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); - Uri uri = null; - try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - } catch (UnsupportedOperationException e) { - System.out.println("Can't write to external media storage."); - try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); - } catch (UnsupportedOperationException ex) { - System.out.println("Can't write to internal media storage."); - this.failPicture("Error capturing image - no media storage found."); - return; - } - } - - // Add compressed version of captured image to returned media store Uri - OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); - bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); - os.close(); - - // Send Uri back to JavaScript for viewing image - this.ctx.sendJavascript("navigator.camera.success('" + uri.toString() + "');"); + else if (destType == FILE_URI) { + this.ctx.sendJavascript("navigator.camera.success('" + uri + "');"); } - } catch (IOException e) { - e.printStackTrace(); - this.failPicture("Error capturing image."); - } + } + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Selection cancelled."); + } + else { + this.failPicture("Selection did not complete!"); + } } - - // If cancelled - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Camera cancelled."); - } - - // If something else - else { - this.failPicture("Did not complete!"); - } } /** diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 3fa921ed..4786d357 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -25,7 +25,6 @@ package com.phonegap; import com.phonegap.api.Plugin; -import java.util.HashMap; import com.phonegap.api.PluginManager; import android.app.Activity; @@ -91,10 +90,8 @@ public class DroidGap extends Activity { private String url; // The initial URL for our app private String baseUrl; // The base of the initial URL for our app - // Variables to manage ActivityResultCallbacks - private int activityResultCallbackCounter = 1000; - private HashMap activityResultCallbacks = new HashMap(); - + private Plugin activityResultCallback = null; // Plugin to call when activity result is received + /** * Called when the activity is first created. * @@ -677,13 +674,11 @@ public class DroidGap extends Activity { * * @param command The command object * @param intent The intent to start - * @return The request code to use for the callback + * @param requestCode The request code that is passed to callback to identify the activity */ - public int startActivityForResult(Plugin command, Intent intent) { - int requestCode = this.activityResultCallbackCounter++; - this.activityResultCallbacks.put(requestCode, command); + public void startActivityForResult(Plugin command, Intent intent, int requestCode) { + this.activityResultCallback = command; super.startActivityForResult(intent, requestCode); - return requestCode; } @Override @@ -698,8 +693,7 @@ public class DroidGap extends Activity { */ protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); - - Plugin callback = this.activityResultCallbacks.remove(requestCode); + Plugin callback = this.activityResultCallback; if (callback != null) { callback.onActivityResult(requestCode, resultCode, intent); } From 0895083f1fc768f328f2fc73740537d227a3266c Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Thu, 16 Sep 2010 12:47:13 -0500 Subject: [PATCH 07/20] Use same option name as iPhone. --- framework/assets/js/camera.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/framework/assets/js/camera.js b/framework/assets/js/camera.js index e7a65ee7..0933c9da 100755 --- a/framework/assets/js/camera.js +++ b/framework/assets/js/camera.js @@ -16,12 +16,13 @@ Camera = function() { * Example: navigator.camera.getPicture(success, fail, * { quality: 80, * destinationType: Camera.DestinationType.DATA_URL, - * pictureSourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) */ Camera.DestinationType = { DATA_URL: 0, // Return base64 encoded string FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) }; +Camera.prototype.DestinationType = Camera.DestinationType; /** * Source to getPicture from. @@ -29,19 +30,20 @@ Camera.DestinationType = { * Example: navigator.camera.getPicture(success, fail, * { quality: 80, * destinationType: Camera.DestinationType.DATA_URL, - * pictureSourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) */ Camera.PictureSourceType = { PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) CAMERA : 1, // Take picture from camera SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) }; +Camera.prototype.PictureSourceType = Camera.PictureSourceType; /** - * Gets a picture from source defined by "options.pictureSourceType", and returns the + * Gets a picture from source defined by "options.sourceType", and returns the * image as defined by the "options.destinationType" option. - * The defaults are pictureSourceType=CAMERA and destinationType=DATA_URL. + * The defaults are sourceType=CAMERA and destinationType=DATA_URL. * * @param {Function} successCallback * @param {Function} errorCallback @@ -72,11 +74,11 @@ Camera.prototype.getPicture = function(successCallback, errorCallback, options) if (this.options.destinationType) { destinationType = this.options.destinationType; } - var pictureSourceType = Camera.PictureSourceType.CAMERA; - if (typeof this.options.pictureSourceType == "number") { - pictureSourceType = this.options.pictureSourceType; + var sourceType = Camera.PictureSourceType.CAMERA; + if (typeof this.options.sourceType == "number") { + sourceType = this.options.sourceType; } - PhoneGap.execAsync(null, null, "Camera", "takePicture", [quality, destinationType, pictureSourceType]); + PhoneGap.execAsync(null, null, "Camera", "takePicture", [quality, destinationType, sourceType]); }; /** From 9d3306ccc5eb83032d693a3d5271e000077bde03 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 17 Sep 2010 15:37:34 -0500 Subject: [PATCH 08/20] Add comments to directory manager source file. --- .../src/com/phonegap/DirectoryManager.java | 147 ++++++++++++++---- 1 file changed, 118 insertions(+), 29 deletions(-) diff --git a/framework/src/com/phonegap/DirectoryManager.java b/framework/src/com/phonegap/DirectoryManager.java index 2b411a6a..6ee0d46e 100644 --- a/framework/src/com/phonegap/DirectoryManager.java +++ b/framework/src/com/phonegap/DirectoryManager.java @@ -6,26 +6,47 @@ import android.os.Environment; import android.os.StatFs; import android.util.Log; +/** + * This class provides file directory utilities. + * All file operations are performed on the SD card. + * + * It is used by the FileUtils class. + */ public class DirectoryManager { - protected static boolean testFileExists (String name){ + /** + * Determine if a file or directory exists. + * + * @param name The name of the file to check. + * @return T=exists, F=not found + */ + protected static boolean testFileExists(String name) { boolean status; - if ((testSaveLocationExists())&&(!name.equals(""))){ + + // If SD card exists + if ((testSaveLocationExists()) && (!name.equals(""))) { File path = Environment.getExternalStorageDirectory(); File newPath = constructFilePaths(path.toString(), name); status = newPath.exists(); - }else{ + } + + // If no SD card + else{ status = false; } return status; } - protected static long getFreeDiskSpace(){ - /* - * gets the available SD card free space or returns -1 if the SD card is not mounted. - */ + /** + * Get the free disk space on the SD card + * + * @return Size in KB or -1 if not available + */ + protected static long getFreeDiskSpace() { String status = Environment.getExternalStorageState(); long freeSpace = 0; + + // If SD card exists if (status.equals(Environment.MEDIA_MOUNTED)) { try { File path = Environment.getExternalStorageDirectory(); @@ -34,44 +55,82 @@ public class DirectoryManager { long availableBlocks = stat.getAvailableBlocks(); freeSpace = availableBlocks*blockSize/1024; } catch (Exception e) {e.printStackTrace(); } - } else { return -1; } + } + + // If no SD card, then return -1 + else { + return -1; + } + return (freeSpace); } - protected static boolean createDirectory(String directoryName){ + /** + * Create directory on SD card. + * + * @param directoryName The name of the directory to create. + * @return T=successful, F=failed + */ + protected static boolean createDirectory(String directoryName) { boolean status; - if ((testSaveLocationExists())&&(!directoryName.equals(""))){ + + // Make sure SD card exists + if ((testSaveLocationExists()) && (!directoryName.equals(""))) { File path = Environment.getExternalStorageDirectory(); File newPath = constructFilePaths(path.toString(), directoryName); status = newPath.mkdir(); status = true; - }else + } + + // If no SD card or invalid dir name + else { status = false; + } return status; } - protected static boolean testSaveLocationExists(){ + /** + * Determine if SD card exists. + * + * @return T=exists, F=not found + */ + protected static boolean testSaveLocationExists() { String sDCardStatus = Environment.getExternalStorageState(); boolean status; - if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)){ + + // If SD card is mounted + if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) { status = true; - }else + } + + // If no SD card + else { status = false; + } return status; } - protected static boolean deleteDirectory(String fileName){ + /** + * Delete directory. + * + * @param fileName The name of the directory to delete + * @return T=deleted, F=could not delete + */ + protected static boolean deleteDirectory(String fileName) { boolean status; SecurityManager checker = new SecurityManager(); - if ((testSaveLocationExists())&&(!fileName.equals(""))){ - + // Make sure SD card exists + if ((testSaveLocationExists()) && (!fileName.equals(""))) { File path = Environment.getExternalStorageDirectory(); File newPath = constructFilePaths(path.toString(), fileName); checker.checkDelete(newPath.toString()); - if(newPath.isDirectory()){ + + // If dir to delete is really a directory + if (newPath.isDirectory()) { String[] listfile = newPath.list(); - // delete all files within the specified directory and then delete the directory + + // Delete all files within the specified directory and then delete the directory try{ for (int i=0; i < listfile.length; i++){ File deletedFile = new File (newPath.toString()+"/"+listfile[i].toString()); @@ -80,27 +139,43 @@ public class DirectoryManager { newPath.delete(); Log.i("DirectoryManager deleteDirectory", fileName); status = true; - }catch (Exception e){ + } + catch (Exception e){ e.printStackTrace(); status = false; } - - }else + } + + // If dir not a directory, then error + else { status = false; - }else + } + } + + // If no SD card + else { status = false; + } return status; } - protected static boolean deleteFile(String fileName){ + /** + * Delete file. + * + * @param fileName The name of the file to delete + * @return T=deleted, F=not deleted + */ + protected static boolean deleteFile(String fileName) { boolean status; SecurityManager checker = new SecurityManager(); - if ((testSaveLocationExists())&&(!fileName.equals(""))){ - + // Make sure SD card exists + if ((testSaveLocationExists()) && (!fileName.equals(""))) { File path = Environment.getExternalStorageDirectory(); File newPath = constructFilePaths(path.toString(), fileName); checker.checkDelete(newPath.toString()); + + // If file to delete is really a file if (newPath.isFile()){ try { Log.i("DirectoryManager deleteFile", fileName); @@ -110,14 +185,28 @@ public class DirectoryManager { se.printStackTrace(); status = false; } - }else + } + // If not a file, then error + else { status = false; - }else + } + } + + // If no SD card + else { status = false; + } return status; } - private static File constructFilePaths (String file1, String file2){ + /** + * Create a new file object from two file paths. + * + * @param file1 Base file path + * @param file2 Remaining file path + * @return File object + */ + private static File constructFilePaths (String file1, String file2) { File newPath; newPath = new File(file1+"/"+file2); return newPath; From b95ad44c18a42c173e5bf24a6bf601d865e29131 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 17 Sep 2010 15:38:18 -0500 Subject: [PATCH 09/20] Add general ERROR to plugin result status. --- framework/src/com/phonegap/api/PluginResult.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/framework/src/com/phonegap/api/PluginResult.java b/framework/src/com/phonegap/api/PluginResult.java index fbfb281f..777f2605 100755 --- a/framework/src/com/phonegap/api/PluginResult.java +++ b/framework/src/com/phonegap/api/PluginResult.java @@ -63,7 +63,8 @@ public class PluginResult { "Malformed url", "IO error", "Invalid action", - "JSON error" + "JSON error", + "Error" }; public enum Status { @@ -74,6 +75,7 @@ public class PluginResult { MALFORMED_URL_EXCEPTION, IO_EXCEPTION, INVALID_ACTION, - JSON_EXCEPTION + JSON_EXCEPTION, + ERROR } } From 00dc18a488189b7c56da206402702d7b0814c000 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 17 Sep 2010 15:41:48 -0500 Subject: [PATCH 10/20] Convert FileUtils to plugin architecture. --- framework/src/com/phonegap/DroidGap.java | 4 +- framework/src/com/phonegap/FileUtils.java | 336 +++++++++++++++------- 2 files changed, 238 insertions(+), 102 deletions(-) diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 4786d357..56eeb0e7 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -82,7 +82,6 @@ public class DroidGap extends Activity { protected Boolean loadInWebView = false; private LinearLayout root; - private FileUtils fs; private BrowserKey mKey; public CallbackServer callbackServer; private PluginManager pluginManager; @@ -266,13 +265,11 @@ public class DroidGap extends Activity { private void bindBrowser(WebView appView) { this.callbackServer = new CallbackServer(); this.pluginManager = new PluginManager(appView, this); - this.fs = new FileUtils(appView, this); this.mKey = new BrowserKey(appView, this); // This creates the new javascript interfaces for PhoneGap appView.addJavascriptInterface(this.pluginManager, "PluginManager"); - appView.addJavascriptInterface(this.fs, "FileUtil"); appView.addJavascriptInterface(this.mKey, "BackButton"); appView.addJavascriptInterface(this.callbackServer, "CallbackServer"); @@ -296,6 +293,7 @@ public class DroidGap extends Activity { this.addService("Camera", "com.phonegap.CameraLauncher"); this.addService("Contacts", "com.phonegap.ContactManager"); this.addService("Crypto", "com.phonegap.CryptoHandler"); + this.addService("File", "com.phonegap.FileUtils"); this.addService("Location", "com.phonegap.GeoBroker"); this.addService("Network Status", "com.phonegap.NetworkManager"); this.addService("Storage", "com.phonegap.Storage"); diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java index 3f54f9b6..1e538080 100644 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -2,123 +2,261 @@ package com.phonegap; import java.io.*; +import org.apache.commons.codec.binary.Base64; +import org.json.JSONArray; +import org.json.JSONException; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + +import android.content.Intent; import android.webkit.WebView; -public class FileUtils { +/** + * This class provides SD card file and directory services to JavaScript. + * Only files on the SD card can be accessed. + */ +public class FileUtils implements Plugin { + + public static int NOT_FOUND_ERR = 8; + public static int SECURITY_ERR = 18; + public static int ABORT_ERR = 20; + + public static int NOT_READABLE_ERR = 24; + public static int ENCODING_ERR = 26; - WebView mView; + WebView webView; // WebView object + DroidGap ctx; // DroidGap object + FileReader f_in; FileWriter f_out; - public FileUtils(WebView view, DroidGap gap) - { - mView = view; + /** + * Constructor. + */ + public FileUtils() { + System.out.println("FileUtils()"); } - - public int testSaveLocationExists(){ - if (DirectoryManager.testSaveLocationExists()) - return 0; - else - return 1; - } - - public long getFreeDiskSpace(){ - long freeDiskSpace=DirectoryManager.getFreeDiskSpace(); - return freeDiskSpace; - } - public int testFileExists(String file){ - if (DirectoryManager.testFileExists(file)) - return 0; - else - return 1; - } - - public int testDirectoryExists(String file){ - if (DirectoryManager.testFileExists(file)) - return 0; - else - return 1; - } - - /** - * Delete a specific directory. - * Everyting in side the directory would be gone. - * TODO: JavaScript Call backs for success and error handling + /** + * Sets the context of the Command. This can then be used to do things like + * get file paths associated with the Activity. + * + * @param ctx The context of the main Activity. */ - public int deleteDirectory (String dir){ - if (DirectoryManager.deleteDirectory(dir)) - return 0; - else - return 1; - } - + public void setContext(DroidGap ctx) { + this.ctx = ctx; + } - /** - * Delete a specific file. - * TODO: JavaScript Call backs for success and error handling + /** + * Sets the main View of the application, this is the WebView within which + * a PhoneGap app runs. + * + * @param webView The PhoneGap WebView */ - public int deleteFile (String file){ - if (DirectoryManager.deleteFile(file)) - return 0; - else - return 1; - } - + public void setView(WebView webView) { + this.webView = webView; + } - /** - * Create a new directory. - * TODO: JavaScript Call backs for success and error handling + /** + * Executes the request and returns CommandResult. + * + * @param action The command to execute. + * @param args JSONArry of arguments for the command. + * @return A CommandResult object with a status and message. */ - public int createDirectory(String dir){ - if (DirectoryManager.createDirectory(dir)) - return 0; - else - return 1; - } - - public String read(String filename) - { - String data = ""; - String output = ""; - try { - FileInputStream fstream = new FileInputStream(filename); - DataInputStream in = new DataInputStream(fstream); - while (in.available() !=0) - { - data += in.readLine(); - } - - } catch (FileNotFoundException e) { - data = "FAIL: File not found"; - } catch (IOException e) { - data = "FAIL: IO ERROR"; - } + public PluginResult execute(String action, JSONArray args) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + //System.out.println("FileUtils.execute("+action+")"); - //mView.loadUrl("javascript:navigator.FileReader.hasRead('" + data + "')"); + try { + if (action.equals("testSaveLocationExists")) { + boolean b = DirectoryManager.testSaveLocationExists(); + return new PluginResult(status, b); + } + else if (action.equals("getFreeDiskSpace")) { + long l = DirectoryManager.getFreeDiskSpace(); + return new PluginResult(status, l); + } + else if (action.equals("testFileExists")) { + boolean b = DirectoryManager.testFileExists(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("testDirectoryExists")) { + boolean b = DirectoryManager.testFileExists(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("deleteDirectory")) { + boolean b = DirectoryManager.deleteDirectory(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("deleteFile")) { + boolean b = DirectoryManager.deleteFile(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("createDirectory")) { + boolean b = DirectoryManager.createDirectory(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("readAsText")) { + try { + String s = this.readAsText(args.getString(0), args.getString(1)); + return new PluginResult(status, s); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("readAsDataURL")) { + try { + String s = this.readAsDataURL(args.getString(0)); + return new PluginResult(status, s); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("writeAsText")) { + try { + this.writeAsText(args.getString(0), args.getString(1), args.getBoolean(2)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + return new PluginResult(status, result); + } catch (JSONException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + /** + * Identifies if action to be executed returns a value and should be run synchronously. + * + * @param action The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + if (action.equals("read")) { + return false; + } + else if (action.equals("write")) { + return false; + } + return true; + } + + /** + * Called when the system is about to start resuming a previous activity. + */ + public void onPause() { + } + + /** + * Called when the activity will start interacting with the user. + */ + public void onResume() { + } + + /** + * Called by AccelBroker when listener is to be shut down. + * Stop listener. + */ + public void onDestroy() { + } + + /** + * Called when an activity you launched exits, giving you the requestCode you started it with, + * the resultCode it returned, and any additional data from it. + * + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + */ + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + } + + //-------------------------------------------------------------------------- + // LOCAL METHODS + //-------------------------------------------------------------------------- + + /** + * Read content of text file. + * + * @param filename The name of the file. + * @param encoding The encoding to return contents as. Typical value is UTF-8. + * (see http://www.iana.org/assignments/character-sets) + * @return Contents of file. + * @throws FileNotFoundException, IOException + */ + public String readAsText(String filename, String encoding) throws FileNotFoundException, IOException { + System.out.println("FileUtils.readAsText("+filename+", "+encoding+")"); + StringBuilder data = new StringBuilder(); + FileInputStream fis = new FileInputStream(filename); + BufferedReader reader = new BufferedReader(new InputStreamReader(fis, encoding), 1024); + String line; + while ((line = reader.readLine()) != null) { + data.append(line); + } + return data.toString(); + } + + /** + * Read content of text file and return as base64 encoded data url. + * + * @param filename The name of the file. + * @return Contents of file = data:;base64, + * @throws FileNotFoundException, IOException + */ + public String readAsDataURL(String filename) throws FileNotFoundException, IOException { + byte[] bytes = new byte[1000]; + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filename), 1024); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int numRead = 0; + while ((numRead = bis.read(bytes, 0, 1000)) >= 0) { + bos.write(bytes, 0, numRead); + } + + // Determine content type from file name + // TODO + String contentType = ""; + + byte[] base64 = Base64.encodeBase64(bos.toByteArray()); + String data = "data:" + contentType + ";base64," + new String(base64); return data; } - public int write(String filename, String data, boolean append) - { - String FilePath= filename; - try { - byte [] rawData = data.getBytes(); - ByteArrayInputStream in = new ByteArrayInputStream(rawData); - FileOutputStream out= new FileOutputStream(FilePath, append); - byte buff[] = new byte[rawData.length]; - in.read(buff, 0, buff.length); - out.write(buff, 0, rawData.length); - out.flush(); - out.close(); - //mView.loadUrl("javascript:navigator.FileReader.onsuccess('File written')"); - } catch (Exception e) { - //mView.loadUrl("javascript:navigator.FileReader.onerror('Fail')"); - // So, do we just return -1 at this point! - return -1; - } - return 0; + /** + * Write contents of file. + * + * @param filename The name of the file. + * @param data The contents of the file. + * @param append T=append, F=overwrite + * @throws FileNotFoundException, IOException + */ + public void writeAsText(String filename, String data, boolean append) throws FileNotFoundException, IOException { + String FilePath= filename; + byte [] rawData = data.getBytes(); + ByteArrayInputStream in = new ByteArrayInputStream(rawData); + FileOutputStream out= new FileOutputStream(FilePath, append); + byte buff[] = new byte[rawData.length]; + in.read(buff, 0, buff.length); + out.write(buff, 0, rawData.length); + out.flush(); + out.close(); } From 2d4a321cc167f9cbae8512f19081f0e20cb0c6c9 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 17 Sep 2010 15:43:20 -0500 Subject: [PATCH 11/20] Update files.js to be closer to iPhone impl and W3C File API working draft at http://www.w3.org/TR/FileAPI/. --- framework/assets/js/file.js | 601 +++++++++++++++++++++++------------- 1 file changed, 393 insertions(+), 208 deletions(-) mode change 100644 => 100755 framework/assets/js/file.js diff --git a/framework/assets/js/file.js b/framework/assets/js/file.js old mode 100644 new mode 100755 index e32bbfd7..29c88980 --- a/framework/assets/js/file.js +++ b/framework/assets/js/file.js @@ -1,226 +1,411 @@ +/** + * This class provides generic read and write access to the mobile device file system. + * They are not used to read files from a server. + */ +/** + * List of files + */ +function FileList() { + this.files = {}; +}; +/** + * Describes a single file in a FileList + */ +function File() { + this.name = null; + this.type = null; + this.urn = null; +}; -PhoneGap.addConstructor(function() { if (typeof navigator.fileMgr == "undefined") navigator.fileMgr = new FileMgr();}); +/** + * Create an event object since we can't set target on DOM event. + * + * @param type + * @param target + * + */ +File._createEvent = function(type, target) { + // Can't create event object, since we can't set target (its readonly) + //var evt = document.createEvent('Events'); + //evt.initEvent("onload", false, false); + var evt = {"type": type}; + evt.target = target; + return evt; +}; + +function FileError() { + // File error codes + // Found in DOMException + this.NOT_FOUND_ERR = 8; + this.SECURITY_ERR = 18; + this.ABORT_ERR = 20; + + // Added by this specification + this.NOT_READABLE_ERR = 24; + this.ENCODING_ERR = 26; + + this.code = null; +}; + +//----------------------------------------------------------------------------- +// File manager +//----------------------------------------------------------------------------- + +function FileMgr() { +}; + +FileMgr.prototype.getFileBasePaths = function() { +}; + +FileMgr.prototype.testSaveLocationExists = function(successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "testSaveLocationExists", []); +}; + +FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "testFileExists", [fileName]); +}; + +FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]); +}; + +FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "createDirectory", [dirName]); +}; + +FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "deleteDirectory", [dirName]); +}; + +FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "deleteFile", [fileName]); +}; + +FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "getFreeDiskSpace", []); +}; + +FileMgr.prototype.writeAsText = function(fileName, data, append, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "writeAsText", [fileName, data, append]); +}; + +FileMgr.prototype.readAsText = function(fileName, encoding, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "readAsText", [fileName, encoding]); +}; + +FileMgr.prototype.readAsDataURL = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "readAsDataURL", [fileName]); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.fileMgr == "undefined") navigator.fileMgr = new FileMgr(); +}); + +//----------------------------------------------------------------------------- +// File Reader +//----------------------------------------------------------------------------- +// TODO: All other FileMgr function operate on the SD card as root. However, +// for FileReader & FileWriter the root is not SD card. Should this be changed? + +/** + * This class reads the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To read from the SD card, the file name is "sdcard/my_file.txt" + */ +function FileReader() { + this.fileName = ""; + + this.readyState = 0; + + // File data + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onloadstart = null; // When the read starts. + this.onprogress = null; // While reading (and decoding) file or fileBlob data, and reporting partial file data (progess.loaded/progress.total) + this.onload = null; // When the read has successfully completed. + this.onerror = null; // When the read has failed (see errors). + this.onloadend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the read has been aborted. For instance, by invoking the abort() method. +}; + +// States +FileReader.EMPTY = 0; +FileReader.LOADING = 1; +FileReader.DONE = 2; + +/** + * Abort reading file. + */ +FileReader.prototype.abort = function() { + this.readyState = FileReader.DONE; + + // If abort callback + if (typeof this.onabort == "function") { + var evt = File._createEvent("abort", this); + this.onabort(evt); + } + + // TODO: Anything else to do? Maybe sent to native? +}; + +/** + * Read text file. + * + * @param file The name of the file + * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) + */ +FileReader.prototype.readAsText = function(file, encoding) { + this.fileName = file; + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart == "function") { + var evt = File._createEvent("loadstart", this); + this.onloadstart(evt); + } + + // Default encoding is UTF-8 + var enc = encoding ? encoding : "UTF-8"; + + var me = this; + + // Read file + navigator.fileMgr.readAsText(file, enc, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileReader.DONE; + + // If onload callback + if (typeof me.onload == "function") { + var evt = File._createEvent("load", me); + me.onload(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileReader.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + } + ); +}; /** - * This class provides iPhone read and write access to the mobile device file system. - * Based loosely on http://www.w3.org/TR/2009/WD-FileAPI-20091117/#dfn-empty - */ -function FileMgr() -{ - this.fileWriters = {}; // empty maps - this.fileReaders = {}; - - this.docsFolderPath = "../../Documents"; - this.tempFolderPath = "../../tmp"; - this.freeDiskSpace = -1; - this.getFileBasePaths(); -} - -// private, called from Native Code -FileMgr.prototype._setPaths = function(docs,temp) -{ - this.docsFolderPath = docs; - this.tempFolderPath = temp; -} - -// private, called from Native Code -FileMgr.prototype._setFreeDiskSpace = function(val) -{ - this.freeDiskSpace = val; -} - - -// FileWriters add/remove -// called internally by writers -FileMgr.prototype.addFileWriter = function(filePath,fileWriter) -{ - this.fileWriters[filePath] = fileWriter; -} - -FileMgr.prototype.removeFileWriter = function(filePath) -{ - this.fileWriters[filePath] = null; -} - -// File readers add/remove -// called internally by readers -FileMgr.prototype.addFileReader = function(filePath,fileReader) -{ - this.fileReaders[filePath] = fileReader; -} - -FileMgr.prototype.removeFileReader = function(filePath) -{ - this.fileReaders[filePath] = null; -} - -/******************************************* + * Read file and return data as a base64 encoded data url. + * A data url is of the form: + * data:[][;base64], * - * private reader callback delegation - * called from native code + * @param file The name of the file */ -FileMgr.prototype.reader_onloadstart = function(filePath,result) -{ - this.fileReaders[filePath].onloadstart(result); -} +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = file; -FileMgr.prototype.reader_onprogress = function(filePath,result) -{ - this.fileReaders[filePath].onprogress(result); -} + // LOADING state + this.readyState = FileReader.LOADING; -FileMgr.prototype.reader_onload = function(filePath,result) -{ - this.fileReaders[filePath].result = unescape(result); - this.fileReaders[filePath].onload(this.fileReaders[filePath].result); -} + // If loadstart callback + if (typeof this.onloadstart == "function") { + var evt = File._createEvent("loadstart", this); + this.onloadstart(evt); + } -FileMgr.prototype.reader_onerror = function(filePath,err) -{ - this.fileReaders[filePath].result = err; - this.fileReaders[filePath].onerror(err); -} + var me = this; -FileMgr.prototype.reader_onloadend = function(filePath,result) -{ - this.fileReaders[filePath].onloadend(result); -} + // Read file + navigator.fileMgr.readAsDataURL(file, -/******************************************* + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileReader.DONE; + + // If onload callback + if (typeof me.onload == "function") { + var evt = File._createEvent("load", me); + me.onload(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileReader.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + } + ); +}; + +/** + * Read file and return data as a binary data. * - * private writer callback delegation - * called from native code -*/ -FileMgr.prototype.writer_onerror = function(filePath,err) -{ - this.fileWriters[filePath].onerror(err); -} - -FileMgr.prototype.writer_oncomplete = function(filePath,result) -{ - this.fileWriters[filePath].oncomplete(result); // result contains bytes written -} - - -FileMgr.prototype.getFileBasePaths = function() -{ - //PhoneGap.exec("File.getFileBasePaths"); -} - -FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) -{ - var test = FileUtil.testFileExists(fileName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.testDirectoryExists(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.createDirectory(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.deleteDirectory(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - FileUtil.deleteFile(fileName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) -{ - if(this.freeDiskSpace > 0) - { - return this.freeDiskSpace; - } - else - { - this.successCallback = successCallback; - this.errorCallback = errorCallback; - this.freeDiskSpace = FileUtil.getFreeDiskSpace(); - (this.freeDiskSpace > 0) ? successCallback() : errorCallback(); - } -} - - -// File Reader - - -function FileReader() -{ - this.fileName = ""; - this.result = null; - this.onloadstart = null; - this.onprogress = null; - this.onload = null; - this.onerror = null; - this.onloadend = null; -} - - -FileReader.prototype.abort = function() -{ - // Not Implemented -} - -FileReader.prototype.readAsText = function(file) -{ - if(this.fileName && this.fileName.length > 0) - { - navigator.fileMgr.removeFileReader(this.fileName,this); - } - this.fileName = file; - navigator.fileMgr.addFileReader(this.fileName,this); - - return FileUtil.read(this.fileName); -} + * @param file The name of the file + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; +//----------------------------------------------------------------------------- // File Writer +//----------------------------------------------------------------------------- -function FileWriter() -{ - this.fileName = ""; - this.result = null; - this.readyState = 0; // EMPTY - this.result = null; - this.onerror = null; - this.oncomplete = null; -} +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + */ +function FileWriter() { + this.fileName = ""; + this.result = null; + this.readyState = 0; // EMPTY + this.result = null; + this.onerror = null; + this.oncomplete = null; +}; + +FileWriter.prototype.writeAsText = function(file, text, bAppend) { + if (bAppend != true) { + bAppend = false; // for null values + } + + this.fileName = file; + + // LOADING state + this.readyState = FileReader.LOADING; + + var me = this; + + // Read file + navigator.fileMgr.writeAsText(file, text, bAppend, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileReader.DONE; + + // If oncomplete callback + if (typeof me.oncomplete == "function") { + var evt = File._createEvent("complete", me); + me.oncomplete(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileReader.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + } + ); + +}; -FileWriter.prototype.writeAsText = function(file,text,bAppend) -{ - if(this.fileName && this.fileName.length > 0) - { - navigator.fileMgr.removeFileWriter(this.fileName,this); - } - this.fileName = file; - if(bAppend != true) - { - bAppend = false; // for null values - } - navigator.fileMgr.addFileWriter(file,this); - this.readyState = 0; // EMPTY - var call = FileUtil.write(file, text, bAppend); - this.result = null; -} From 0ed522481fe61c6f1911f11b26a75d450ee673f8 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 17 Sep 2010 16:05:05 -0500 Subject: [PATCH 12/20] Read and write operations are async. --- framework/src/com/phonegap/FileUtils.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) mode change 100644 => 100755 framework/src/com/phonegap/FileUtils.java diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java old mode 100644 new mode 100755 index 1e538080..598c23e9 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -149,10 +149,13 @@ public class FileUtils implements Plugin { * @return T=returns value */ public boolean isSynch(String action) { - if (action.equals("read")) { + if (action.equals("readAsText")) { return false; } - else if (action.equals("write")) { + else if (action.equals("readAsDataURL")) { + return false; + } + else if (action.equals("writeAsText")) { return false; } return true; From eff7c92dae13660d46a4bc85056b6e281be82f61 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 17 Sep 2010 16:17:06 -0500 Subject: [PATCH 13/20] FileWriter should use its own states object. --- framework/assets/js/file.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/framework/assets/js/file.js b/framework/assets/js/file.js index 29c88980..b4037a9c 100755 --- a/framework/assets/js/file.js +++ b/framework/assets/js/file.js @@ -349,6 +349,11 @@ function FileWriter() { this.oncomplete = null; }; +// States +FileWriter.EMPTY = 0; +FileWriter.LOADING = 1; +FileWriter.DONE = 2; + FileWriter.prototype.writeAsText = function(file, text, bAppend) { if (bAppend != true) { bAppend = false; // for null values @@ -357,7 +362,7 @@ FileWriter.prototype.writeAsText = function(file, text, bAppend) { this.fileName = file; // LOADING state - this.readyState = FileReader.LOADING; + this.readyState = FileWriter.LOADING; var me = this; @@ -368,7 +373,7 @@ FileWriter.prototype.writeAsText = function(file, text, bAppend) { function(r) { // If DONE (cancelled), then don't do anything - if (me.readyState == FileReader.DONE) { + if (me.readyState == FileWriter.DONE) { return; } @@ -376,7 +381,7 @@ FileWriter.prototype.writeAsText = function(file, text, bAppend) { me.result = r; // DONE state - me.readyState = FileReader.DONE; + me.readyState = FileWriter.DONE; // If oncomplete callback if (typeof me.oncomplete == "function") { @@ -389,7 +394,7 @@ FileWriter.prototype.writeAsText = function(file, text, bAppend) { function(e) { // If DONE (cancelled), then don't do anything - if (me.readyState == FileReader.DONE) { + if (me.readyState == FileWriter.DONE) { return; } @@ -397,7 +402,7 @@ FileWriter.prototype.writeAsText = function(file, text, bAppend) { me.error = e; // DONE state - me.readyState = FileReader.DONE; + me.readyState = FileWriter.DONE; // If onerror callback if (typeof me.onerror == "function") { From a59cad68e25ab26b8d91377dbdb3f6d2d0ae4c22 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 17 Sep 2010 16:53:52 -0500 Subject: [PATCH 14/20] Device returns string, but for some reason emulator returns object - so convert to string. --- framework/assets/js/phonegap.js.base | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index af0fb556..88a790d9 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -286,10 +286,12 @@ PhoneGap.execAsync = function(success, fail, clazz, action, args) { if (success || fail) { PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; } - var r = PluginManager.exec(clazz, action, callbackId, this.stringify(args), true); + + // Note: Device returns string, but for some reason emulator returs object - so convert to string. + var r = ""+PluginManager.exec(clazz, action, callbackId, this.stringify(args), true); // If a result was returned - if ((typeof r == "string") && (r.length > 0)) { + if (r.length > 0) { eval("var v="+r+";"); // If status is OK, then return value back to caller From ace84227cca01e0894f7d90c9065174a0a15b600 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 20 Sep 2010 15:39:54 -0500 Subject: [PATCH 15/20] Fix problem with deviceready being called before device properties are guaranteed to be set. --- framework/assets/js/device.js | 30 +++++++++++-- framework/assets/js/phonegap.js.base | 67 +++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 8 deletions(-) mode change 100644 => 100755 framework/assets/js/device.js diff --git a/framework/assets/js/device.js b/framework/assets/js/device.js old mode 100644 new mode 100755 index 450a0e32..a4f754f2 --- a/framework/assets/js/device.js +++ b/framework/assets/js/device.js @@ -12,22 +12,46 @@ function Device() { this.phonegap = null; var me = this; - PhoneGap.execAsync( + this.getInfo( function(info) { me.available = true; me.platform = info.platform; me.version = info.version; me.uuid = info.uuid; me.phonegap = info.phonegap; + PhoneGap.onPhoneGapInfoReady.fire(); }, function(e) { me.available = false; console.log("Error initializing PhoneGap: " + e); alert("Error initializing PhoneGap: "+e); - }, - "Device", "getDeviceInfo", []); + }); } +/** + * Get device info + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + */ +Device.prototype.getInfo = function(successCallback, errorCallback) { + + // successCallback required + if (typeof successCallback != "function") { + console.log("Device Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Device Error: errorCallback is not a function"); + return; + } + + // Get info + PhoneGap.execAsync(successCallback, errorCallback, "Device", "getDeviceInfo", []); +}; + /* * This is only for Android. * diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index 88a790d9..ff503b01 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -1,4 +1,25 @@ +/** + * The order of events during page load and PhoneGap startup is as follows: + * + * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. + * window.onload Browser's body onload event. + * onNativeReady Internal event that indicates the PhoneGap native side is ready. + * onPhoneGapInit Internal event that kicks off creatation all PhoneGap JavaScript objects (runs constructors) + * onPhoneGapReady Internal event fired when all PhoneGap JavaScript ojbects have been created + * onPhoneGapInfoReady Internal event fired when device properties are available + * onDeviceReady User event fired to indicate that PhoneGap is ready + * onResume User event fired to indicate a start/resume lifecycle event + * + * The only PhoneGap events that user code should register for are: + * onDeviceReady + * onResume + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + */ + if (typeof(DeviceInfo) != 'object') DeviceInfo = {}; @@ -124,7 +145,7 @@ PhoneGap.available = DeviceInfo.uuid != undefined; * @param {Function} func The function callback you want run once PhoneGap is initialized */ PhoneGap.addConstructor = function(func) { - PhoneGap.onDeviceReady.subscribeOnce(function() { + PhoneGap.onPhoneGapInit.subscribeOnce(function() { try { func(); } catch(e) { @@ -162,6 +183,23 @@ PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); */ PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); +/** + * onPhoneGapInit channel is fired when the web page is fully loaded and + * PhoneGap native code has been initialized. + */ +PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); + +/** + * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. + */ +PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); + +/** + * onPhoneGapInfoReady channel is fired when the PhoneGap device properties + * has been set. + */ +PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); + /** * onResume channel is fired when the PhoneGap native code * resumes. @@ -180,22 +218,41 @@ PhoneGap.onPause = new PhoneGap.Channel('onPause'); if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } /** - * onDeviceReady is fired only after both onDOMContentLoaded and - * onNativeReady have fired. + * onDeviceReady is fired only after all PhoneGap objects are created and + * the device properties are set. */ PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); +/** + * Start listening for XHR callbacks onDeviceReady + */ PhoneGap.onDeviceReady.subscribeOnce(function() { PhoneGap.JSCallback(); }); +/** + * Create all PhoneGap objects once page has fully loaded and native side is ready. + */ +PhoneGap.Channel.join(function() { + + // Run PhoneGap constructors + PhoneGap.onPhoneGapInit.fire(); + + // Fire event to notify that all objects are created + PhoneGap.onPhoneGapReady.fire(); + +}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); + +/** + * Fire onDeviceReady event once all constructors have run and PhoneGap info has been + * received from native side. + */ PhoneGap.Channel.join(function() { PhoneGap.onDeviceReady.fire(); // Fire the onresume event, since first one happens before JavaScript is loaded PhoneGap.onResume.fire(); -}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); - +}, [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]); // Listen for DOMContentLoaded and notify our channel subscribers document.addEventListener('DOMContentLoaded', function() { From b079a243730f044cdad2ff92bdd13eebf81c3ee0 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 20 Sep 2010 15:48:37 -0500 Subject: [PATCH 16/20] Need to listen for XHR callbacks before constructors are run, since constructors could call native code that returns data in callback. --- framework/assets/js/phonegap.js.base | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index ff503b01..41a7064a 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -223,18 +223,15 @@ if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } */ PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); -/** - * Start listening for XHR callbacks onDeviceReady - */ -PhoneGap.onDeviceReady.subscribeOnce(function() { - PhoneGap.JSCallback(); -}); /** * Create all PhoneGap objects once page has fully loaded and native side is ready. */ PhoneGap.Channel.join(function() { + // Start listening for XHR callbacks + PhoneGap.JSCallback(); + // Run PhoneGap constructors PhoneGap.onPhoneGapInit.fire(); From 1a9173d2c35ff93d9deb7f0716303595bc86b493 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 20 Sep 2010 21:09:35 -0500 Subject: [PATCH 17/20] Added comments. --- framework/assets/js/phonegap.js.base | 45 +++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index 41a7064a..dbfe0343 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -3,10 +3,10 @@ * The order of events during page load and PhoneGap startup is as follows: * * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. - * window.onload Browser's body onload event. + * window.onload Body onload event. * onNativeReady Internal event that indicates the PhoneGap native side is ready. - * onPhoneGapInit Internal event that kicks off creatation all PhoneGap JavaScript objects (runs constructors) - * onPhoneGapReady Internal event fired when all PhoneGap JavaScript ojbects have been created + * onPhoneGapInit Internal event that kicks off creation of all PhoneGap JavaScript objects (runs constructors). + * onPhoneGapReady Internal event fired when all PhoneGap JavaScript objects have been created * onPhoneGapInfoReady Internal event fired when device properties are available * onDeviceReady User event fired to indicate that PhoneGap is ready * onResume User event fired to indicate a start/resume lifecycle event @@ -256,7 +256,6 @@ document.addEventListener('DOMContentLoaded', function() { PhoneGap.onDOMContentLoaded.fire(); }, false); - // Intercept calls to document.addEventListener and watch for deviceready PhoneGap.m_document_addEventListener = document.addEventListener; @@ -313,6 +312,7 @@ PhoneGap.callbacks = {}; * @param {String} command Command to be run in PhoneGap, e.g. "ClassName.method" * @param {String[]} [args] Zero or more arguments to pass to the method */ +// TODO: Not used anymore, should be removed. PhoneGap.exec = function(clazz, action, args) { try { var callbackId = 0; @@ -334,15 +334,29 @@ PhoneGap.exec = function(clazz, action, args) { } }; -PhoneGap.execAsync = function(success, fail, clazz, action, args) { +/** + * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. + * The native side can return: + * Synchronous: PluginResult object as a JSON string + * Asynchrounous: Empty string "" + * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, + * depending upon the result of the action. + * + * @param {Function} success The success callback + * @param {Function} fail The fail callback + * @param {String} service The name of the service to use + * @param {String} action Action to be run in PhoneGap + * @param {String[]} [args] Zero or more arguments to pass to the method + */ +PhoneGap.execAsync = function(success, fail, service, action, args) { try { - var callbackId = clazz + PhoneGap.callbackId++; + var callbackId = service + PhoneGap.callbackId++; if (success || fail) { PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; } - // Note: Device returns string, but for some reason emulator returs object - so convert to string. - var r = ""+PluginManager.exec(clazz, action, callbackId, this.stringify(args), true); + // Note: Device returns string, but for some reason emulator returns object - so convert to string. + var r = ""+PluginManager.exec(service, action, callbackId, this.stringify(args), true); // If a result was returned if (r.length > 0) { @@ -376,6 +390,12 @@ PhoneGap.execAsync = function(success, fail, clazz, action, args) { } }; +/** + * Called by native code when returning successful result from an action. + * + * @param callbackId + * @param args + */ PhoneGap.callbackSuccess = function(callbackId, args) { if (PhoneGap.callbacks[callbackId]) { try { @@ -390,6 +410,12 @@ PhoneGap.callbackSuccess = function(callbackId, args) { } }; +/** + * Called by native code when returning error result from an action. + * + * @param callbackId + * @param args + */ PhoneGap.callbackError = function(callbackId, args) { if (PhoneGap.callbacks[callbackId]) { try { @@ -412,6 +438,7 @@ PhoneGap.callbackError = function(callbackId, args) { * url, which will be turned into a dictionary on the other end. * @private */ +// TODO: Is this used? PhoneGap.run_command = function() { if (!PhoneGap.available || !PhoneGap.queue.ready) return; @@ -451,6 +478,8 @@ PhoneGap.run_command = function() { }; /** + * This is only for Android. + * * Internal function that uses XHR to call into PhoneGap Java code and retrieve * any JavaScript code that needs to be run. This is used for callbacks from * Java to JavaScript. From 063e189bb7b7b84d72e6863f0f43918e70697960 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 20 Sep 2010 22:25:57 -0500 Subject: [PATCH 18/20] Change isReachable() to return NetworkStatus constant to reachableCallback(reachability) as specified in the API documentation. --- framework/assets/js/network.js | 39 ++----------- .../src/com/phonegap/NetworkManager.java | 56 +++++++++++++++---- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/framework/assets/js/network.js b/framework/assets/js/network.js index 720b8529..b3fb1f40 100755 --- a/framework/assets/js/network.js +++ b/framework/assets/js/network.js @@ -4,8 +4,8 @@ * @constructor */ function NetworkStatus() { - this.code = null; - this.message = ""; + //this.code = null; + //this.message = ""; }; NetworkStatus.NOT_REACHABLE = 0; @@ -42,38 +42,11 @@ Network.prototype.updateReachability = function(reachability) { * @param {Object} options (isIpAddress:boolean) */ Network.prototype.isReachable = function(uri, callback, options) { - - // callback required - if (typeof callback != "function") { - console.log("Network Error: callback is not a function"); - return; + var isIpAddress = false; + if (options && options.isIpAddress) { + isIpAddress = options.isIpAddress; } - - PhoneGap.execAsync( - function(status) { - - // If reachable, the check for wifi vs carrier - if (status) { - PhoneGap.execAsync( - function(wifi) { - var s = new NetworkStatus(); - if (wifi) { - s.code = NetworkStatus.REACHABLE_VIA_WIFI_NETWORK; - } - else { - s.code = NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK; - } - callback(s); - }, null, "Network Status", "isWifiActive", []); - } - - // If not - else { - var s = new NetworkStatus(); - s.code = NetworkStatus.NOT_REACHABLE; - callback(s); - } - }, null, "Network Status", "isReachable", [uri]); + PhoneGap.execAsync(callback, null, "Network Status", "isReachable", [uri, isIpAddress]); }; PhoneGap.addConstructor(function() { diff --git a/framework/src/com/phonegap/NetworkManager.java b/framework/src/com/phonegap/NetworkManager.java index caf6efba..82c75fe5 100755 --- a/framework/src/com/phonegap/NetworkManager.java +++ b/framework/src/com/phonegap/NetworkManager.java @@ -14,6 +14,11 @@ import android.net.*; import android.webkit.WebView; public class NetworkManager implements Plugin { + + public static int NOT_REACHABLE = 0; + public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; + public static int REACHABLE_VIA_WIFI_NETWORK = 2; + WebView webView; // WebView object DroidGap ctx; // DroidGap object @@ -67,8 +72,8 @@ public class NetworkManager implements Plugin { return new PluginResult(status, b); } else if (action.equals("isReachable")) { - boolean b = this.isReachable(args.getString(0)); - return new PluginResult(status, b); + int i = this.isReachable(args.getString(0), args.getBoolean(1)); + return new PluginResult(status, i); } return new PluginResult(status, result); } catch (JSONException e) { @@ -122,6 +127,11 @@ public class NetworkManager implements Plugin { // LOCAL METHODS //-------------------------------------------------------------------------- + /** + * Determine if a network connection exists. + * + * @return + */ public boolean isAvailable() { NetworkInfo info = sockMan.getActiveNetworkInfo(); boolean conn = false; @@ -131,6 +141,11 @@ public class NetworkManager implements Plugin { return conn; } + /** + * Determine if a WIFI connection exists. + * + * @return + */ public boolean isWifiActive() { NetworkInfo info = sockMan.getActiveNetworkInfo(); if (info != null) { @@ -140,18 +155,37 @@ public class NetworkManager implements Plugin { return false; } - public boolean isReachable(String uri) { + /** + * Determine if a URI is reachable over the network. + * + * @param uri + * @param isIpAddress + * @return + */ + public int isReachable(String uri, boolean isIpAddress) { + int reachable = NOT_REACHABLE; + if (uri.indexOf("http://") == -1) { uri = "http://" + uri; } - boolean reached = isAvailable(); - try { - DefaultHttpClient httpclient = new DefaultHttpClient(); - HttpGet httpget = new HttpGet(uri); - httpclient.execute(httpget); - } catch (Exception e) { - reached = false; + + if (isAvailable()) { + try { + DefaultHttpClient httpclient = new DefaultHttpClient(); + HttpGet httpget = new HttpGet(uri); + httpclient.execute(httpget); + + if (isWifiActive()) { + reachable = REACHABLE_VIA_WIFI_NETWORK; + } + else { + reachable = REACHABLE_VIA_CARRIER_DATA_NETWORK; + } + } catch (Exception e) { + reachable = NOT_REACHABLE; + } } - return reached; + + return reachable; } } From edfa41c9f95cadcc07a920dae761a5751d32d71b Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Wed, 22 Sep 2010 14:47:52 -0500 Subject: [PATCH 19/20] Update geolocation to follow W3C spec, add comments, add error checking. --- framework/assets/js/geolocation.js | 211 ++++++++++++------ framework/src/com/phonegap/DroidGap.java | 2 +- framework/src/com/phonegap/GeoBroker.java | 65 +++++- framework/src/com/phonegap/GeoListener.java | 133 ++++++----- framework/src/com/phonegap/GpsListener.java | 149 +++++++++---- .../src/com/phonegap/NetworkListener.java | 145 ++++++++---- 6 files changed, 474 insertions(+), 231 deletions(-) mode change 100644 => 100755 framework/assets/js/geolocation.js mode change 100644 => 100755 framework/src/com/phonegap/GeoBroker.java mode change 100644 => 100755 framework/src/com/phonegap/GeoListener.java mode change 100644 => 100755 framework/src/com/phonegap/GpsListener.java mode change 100644 => 100755 framework/src/com/phonegap/NetworkListener.java diff --git a/framework/assets/js/geolocation.js b/framework/assets/js/geolocation.js old mode 100644 new mode 100755 index fec7b884..40f06da4 --- a/framework/assets/js/geolocation.js +++ b/framework/assets/js/geolocation.js @@ -3,85 +3,152 @@ * @constructor */ function Geolocation() { - /** - * The last known GPS position. - */ + + // The last known GPS position. this.lastPosition = null; - this.lastError = null; - this.listeners = null; + + // Geolocation listeners + this.listeners = {}; }; -var geoListeners = []; - -Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) -{ - var position = Geo.getCurrentLocation(); - this.global_success = successCallback; - this.fail = errorCallback; -} - -// Run the global callback -Geolocation.prototype.gotCurrentPosition = function(lat, lng, alt, altacc, head, vel, stamp) -{ - if (lat == "undefined" || lng == "undefined") - { - this.fail(); - } - else - { - coords = new Coordinates(lat, lng, alt, acc, head, vel); - loc = new Position(coords, stamp); - this.lastPosition = loc; - this.global_success(loc); - } -} - -/* -* This turns on the GeoLocator class, which has two listeners. -* The listeners have their own timeouts, and run independently of this process -* In this case, we return the key to the watch hash -*/ - -Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) -{ - var frequency = (options != undefined)? options.frequency : 10000; - - var key = geoListeners.push( {"success" : successCallback, "fail" : errorCallback }) - 1; - - // TO-DO: Get the names of the method and pass them as strings to the Java. - return Geo.start(frequency, key); -} - -/* - * Retrieve and stop this listener from listening to the GPS +/** + * Position error object * + * @param code + * @param message */ -Geolocation.prototype.success = function(key, lat, lng, alt, altacc, head, vel, stamp) -{ - var coords = new Coordinates(lat, lng, alt, acc, head, vel); - var loc = new Position(coords, stamp); - geoListeners[key].success(loc); +function PositionError(code, message) { + this.code = code; + this.message = message; +}; + +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; + +/** + * Asynchronously aquires the current position. + * + * @param {Function} successCallback The function to call when the position data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) + * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) + */ +Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) { + if (navigator._geo.listeners["global"]) { + console.log("Geolocation Error: Still waiting for previous getCurrentPosition() request."); + try { + errorCallback(new PositionError(PositionError.TIMEOUT, "Geolocation Error: Still waiting for previous getCurrentPosition() request.")); + } catch (e) { + } + return; + } + navigator._geo.listeners["global"] = {"success" : successCallback, "fail" : errorCallback }; + PhoneGap.execAsync(null, null, "Geolocation", "getCurrentLocation", []); } -Geolocation.prototype.fail = function(key) -{ - geoListeners[key].fail(); -} - -Geolocation.prototype.clearWatch = function(watchId) -{ - Geo.stop(watchId); -} +/** + * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, + * the successCallback is called with the new location. + * + * @param {Function} successCallback The function to call each time the location data is available + * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ +Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) { + var frequency = (options != undefined)? options.frequency : 10000; + var id = PhoneGap.createUUID(); + navigator._geo.listeners[id] = {"success" : successCallback, "fail" : errorCallback }; + PhoneGap.execAsync(null, null, "Geolocation", "start", [frequency, id]); + return id; +}; + +/* + * Native callback when watch position has a new position. + * + * @param {String} id + * @param {Number} lat + * @param {Number} lng + * @param {Number} alt + * @param {Number} altacc + * @param {Number} head + * @param {Number} vel + * @param {Number} stamp + */ +Geolocation.prototype.success = function(id, lat, lng, alt, altacc, head, vel, stamp) { + var coords = new Coordinates(lat, lng, alt, altacc, head, vel); + var loc = new Position(coords, stamp); + try { + if (lat == "undefined" || lng == "undefined") { + navigator._geo.listeners[id].fail(new PositionError(PositionError.POSITION_UNAVAILABLE, "Lat/Lng are undefined.")); + } + else { + navigator._geo.lastPosition = loc; + navigator._geo.listeners[id].success(loc); + } + } + catch (e) { + console.log("Geolocation Error: Error calling success callback function."); + } + + if (id == "global") { + delete navigator._geo.listeners["global"]; + } +}; + +/** + * Native callback when watch position has an error. + * + * @param {String} id The ID of the watch + * @param {Number} code The error code + * @param {String} msg The error message + */ +Geolocation.prototype.fail = function(id, code, msg) { + try { + navigator._geo.listeners[id].fail(new PositionError(code, msg)); + } + catch (e) { + console.log("Geolocation Error: Error calling error callback function."); + } +}; + +/** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchPosition + */ +Geolocation.prototype.clearWatch = function(id) { + PhoneGap.execAsync(null, null, "Geolocation", "stop", [id]); + delete navigator._geo.listeners[id]; +}; + +/** + * Force the PhoneGap geolocation to be used instead of built-in. + */ +Geolocation.usingPhoneGap = false; +Geolocation.usePhoneGap = function() { + if (Geolocation.usingPhoneGap) { + return; + } + Geolocation.usingPhoneGap = true; + + // Set built-in geolocation methods to our own implementations + // (Cannot replace entire geolocation, but can replace individual methods) + navigator.geolocation.setLocation = navigator._geo.setLocation; + navigator.geolocation.getCurrentPosition = navigator._geo.getCurrentPosition; + navigator.geolocation.watchPosition = navigator._geo.watchPosition; + navigator.geolocation.clearWatch = navigator._geo.clearWatch; + navigator.geolocation.start = navigator._geo.start; + navigator.geolocation.stop = navigator._geo.stop; +}; PhoneGap.addConstructor(function() { - // Taken from Jesse's geo fix (similar problem) in PhoneGap iPhone. Go figure, same browser! - function __proxyObj(origObj, proxyObj, funkList) { - for (var v in funkList) { - origObj[funkList[v]] = proxyObj[funkList[v]]; - } - } - // In the case of Android, we can use the Native Geolocation Object if it exists, so only load this on 1.x devices - if (typeof navigator.geolocation == 'undefined') { - navigator.geolocation = new Geolocation(); - } + navigator._geo = new Geolocation(); + + // No native geolocation object for Android 1.x, so use PhoneGap geolocation + if (typeof navigator.geolocation == 'undefined') { + navigator.geolocation = navigator._geo; + Geolocation.usingPhoneGap = true; + } }); + diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 56eeb0e7..70d15328 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -282,10 +282,10 @@ public class DroidGap extends Activity { Storage cupcakeStorage = (Storage)this.pluginManager.addPlugin("com.phonegap.Storage"); cupcakeStorage.setStorage(appPackage); - this.pluginManager.addPlugin("com.phonegap.GeoBroker"); } + this.addService("Geolocation", "com.phonegap.GeoBroker"); this.addService("Device", "com.phonegap.Device"); this.addService("Accelerometer", "com.phonegap.AccelListener"); this.addService("Compass", "com.phonegap.CompassListener"); diff --git a/framework/src/com/phonegap/GeoBroker.java b/framework/src/com/phonegap/GeoBroker.java old mode 100644 new mode 100755 index 0ea4645a..e799f50c --- a/framework/src/com/phonegap/GeoBroker.java +++ b/framework/src/com/phonegap/GeoBroker.java @@ -1,6 +1,7 @@ package com.phonegap; import java.util.HashMap; +import java.util.Map.Entry; import org.json.JSONArray; import org.json.JSONException; @@ -22,6 +23,7 @@ public class GeoBroker implements Plugin { WebView webView; // WebView object DroidGap ctx; // DroidGap object + // List of gGeolocation listeners private HashMap geoListeners; private GeoListener global; @@ -87,7 +89,8 @@ public class GeoBroker implements Plugin { * @return T=returns value */ public boolean isSynch(String action) { - return false; + // Starting listeners is easier to run on main thread, so don't run async. + return true; } /** @@ -103,10 +106,22 @@ public class GeoBroker implements Plugin { } /** - * Called by AccelBroker when listener is to be shut down. + * Called when the activity is to be shut down. * Stop listener. */ - public void onDestroy() { + public void onDestroy() { + java.util.Set> s = this.geoListeners.entrySet(); + java.util.Iterator> it = s.iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + GeoListener listener = entry.getValue(); + listener.destroy(); + } + this.geoListeners.clear(); + if (this.global != null) { + this.global.destroy(); + } + this.global = null; } /** @@ -125,23 +140,51 @@ public class GeoBroker implements Plugin { // LOCAL METHODS //-------------------------------------------------------------------------- - public void getCurrentLocation() { - //It's supposed to run async! - if (global == null) { - global = new GeoListener("global", this.ctx, 10000, this.webView); + /** + * Get current location. + * The result is returned to JavaScript via a callback. + */ + public void getCurrentLocation() { + + // Create a geolocation listener just for getCurrentLocation and call it "global" + if (this.global == null) { + this.global = new GeoListener("global", this.ctx, 10000, this.webView); } else { - global.start(10000); + this.global.start(10000); } } + /** + * Start geolocation listener and add to listener list. + * + * @param freq Period to retrieve geolocation + * @param key The listener id + * @return + */ public String start(int freq, String key) { - GeoListener listener = new GeoListener(key, this.ctx, freq, this.webView); - geoListeners.put(key, listener); + + // Make sure this listener doesn't already exist + GeoListener listener = geoListeners.get(key); + if (listener == null) { + listener = new GeoListener(key, this.ctx, freq, this.webView); + geoListeners.put(key, listener); + } + + // Start it + listener.start(freq); return key; } + /** + * Stop geolocation listener and remove from listener list. + * + * @param key The listener id + */ public void stop(String key) { - GeoListener geo = geoListeners.get(key); + GeoListener listener = geoListeners.remove(key); + if (listener != null) { + listener.stop(); + } } } diff --git a/framework/src/com/phonegap/GeoListener.java b/framework/src/com/phonegap/GeoListener.java old mode 100644 new mode 100755 index 86d79c8f..b78f6fb1 --- a/framework/src/com/phonegap/GeoListener.java +++ b/framework/src/com/phonegap/GeoListener.java @@ -6,83 +6,112 @@ import android.location.LocationManager; import android.webkit.WebView; public class GeoListener { - String id; - String successCallback; + public static int PERMISSION_DENIED = 1; + public static int POSITION_UNAVAILABLE = 2; + public static int TIMEOUT = 3; + + String id; // Listener ID + String successCallback; // String failCallback; - GpsListener mGps; - NetworkListener mNetwork; - LocationManager mLocMan; - private DroidGap mCtx; - private WebView mAppView; + GpsListener mGps; // GPS listener + NetworkListener mNetwork; // Network listener + LocationManager mLocMan; // Location manager + + private DroidGap ctx; // DroidGap object + @SuppressWarnings("unused") + private WebView mAppView; // Webview object int interval; - GeoListener(String i, DroidGap ctx, int time, WebView appView) { - id = i; - interval = time; - mCtx = ctx; - mGps = null; - mNetwork = null; - mLocMan = (LocationManager) mCtx.getSystemService(Context.LOCATION_SERVICE); + /** + * Constructor. + * + * @param id Listener id + * @param ctx + * @param time Sampling period in msec + * @param appView + */ + GeoListener(String id, DroidGap ctx, int time, WebView appView) { + this.id = id; + this.interval = time; + this.ctx = ctx; + this.mGps = null; + this.mNetwork = null; + this.mLocMan = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); - if (mLocMan.getProvider(LocationManager.GPS_PROVIDER) != null) { - mGps = new GpsListener(mCtx, interval, this); + // If GPS provider, then create and start GPS listener + if (this.mLocMan.getProvider(LocationManager.GPS_PROVIDER) != null) { + this.mGps = new GpsListener(ctx, time, this); } - if (mLocMan.getProvider(LocationManager.NETWORK_PROVIDER) != null) { - mNetwork = new NetworkListener(mCtx, interval, this); + + // If network provider, then create and start network listener + if (this.mLocMan.getProvider(LocationManager.NETWORK_PROVIDER) != null) { + this.mNetwork = new NetworkListener(ctx, time, this); } - mAppView = appView; + this.mAppView = appView; } + /** + * Destroy listener. + */ + public void destroy() { + this.stop(); + } + + /** + * Location found. Send location back to JavaScript. + * + * @param loc + */ void success(Location loc) { - /* - * We only need to figure out what we do when we succeed! - */ - String params; - /* - * Build the giant string to send back to Javascript! - */ - params = loc.getLatitude() + "," + loc.getLongitude() + ", " + loc.getAltitude() + "," + loc.getAccuracy() + "," + loc.getBearing(); - params += "," + loc.getSpeed() + "," + loc.getTime(); - if (id != "global") { - mCtx.sendJavascript("navigator._geo.success(" + id + "," + params + ");"); - } - else { - mCtx.sendJavascript("navigator.geolocation.gotCurrentPosition(" + params + ");"); + String params = loc.getLatitude() + "," + loc.getLongitude() + ", " + loc.getAltitude() + + "," + loc.getAccuracy() + "," + loc.getBearing() + + "," + loc.getSpeed() + "," + loc.getTime(); + + if (id == "global") { this.stop(); } + this.ctx.sendJavascript("navigator._geo.success('" + id + "'," + params + ");"); } - void fail() { - // Do we need to know why? How would we handle this? - if (id != "global") { - mCtx.sendJavascript("navigator._geo.fail(" + id + ");"); - } else { - mCtx.sendJavascript("navigator._geo.fail();"); - } + /** + * Location failed. Send error back to JavaScript. + * + * @param code The error code + * @param msg The error message + */ + void fail(int code, String msg) { + this.ctx.sendJavascript("navigator._geo.fail('" + this.id + "', " + ", " + code + ", '" + msg + "');"); + this.stop(); } + /** + * Start retrieving location. + * + * @param interval + */ void start(int interval) { - if (mGps != null) { - mGps.start(interval); + if (this.mGps != null) { + this.mGps.start(interval); } - if (mNetwork != null) { - mNetwork.start(interval); + if (this.mNetwork != null) { + this.mNetwork.start(interval); } - if (mNetwork == null && mGps == null) { - // Really, how were you going to get the location??? - mCtx.sendJavascript("navigator._geo.fail();"); + if (this.mNetwork == null && this.mGps == null) { + this.fail(POSITION_UNAVAILABLE, "No location providers available."); } } - // This stops the listener + /** + * Stop listening for location. + */ void stop() { - if (mGps != null) { - mGps.stop(); + if (this.mGps != null) { + this.mGps.stop(); } - if (mNetwork != null) { - mNetwork.stop(); + if (this.mNetwork != null) { + this.mNetwork.stop(); } } diff --git a/framework/src/com/phonegap/GpsListener.java b/framework/src/com/phonegap/GpsListener.java old mode 100644 new mode 100755 index a236f563..96326f4e --- a/framework/src/com/phonegap/GpsListener.java +++ b/framework/src/com/phonegap/GpsListener.java @@ -26,81 +26,138 @@ import android.location.Location; import android.location.LocationManager; import android.location.LocationListener; import android.os.Bundle; -import android.util.Log; +/** + * This class handles requests for GPS location services. + * + */ public class GpsListener implements LocationListener { - private Context mCtx; - private Location cLoc; - private LocationManager mLocMan; - private static final String LOG_TAG = "PhoneGap"; - private GeoListener owner; - private boolean hasData = false; + private DroidGap mCtx; // DroidGap object - public GpsListener(Context ctx, int interval, GeoListener m) - { - owner = m; - mCtx = ctx; + private LocationManager mLocMan; // Location manager object + private GeoListener owner; // Geolistener object (parent) + private boolean hasData = false; // Flag indicates if location data is available in cLoc + private Location cLoc; // Last recieved location + private boolean running = false; // Flag indicates if listener is running + + /** + * Constructor. + * Automatically starts listening. + * + * @param ctx + * @param interval + * @param m + */ + public GpsListener(DroidGap ctx, int interval, GeoListener m) { + this.owner = m; + this.mCtx = ctx; + this.mLocMan = (LocationManager) this.mCtx.getSystemService(Context.LOCATION_SERVICE); + this.running = false; this.start(interval); } - public Location getLocation() - { - cLoc = mLocMan.getLastKnownLocation(LocationManager.GPS_PROVIDER); - hasData = true; - return cLoc; + /** + * Get last location. + * + * @return Location object + */ + public Location getLocation() { + this.cLoc = this.mLocMan.getLastKnownLocation(LocationManager.GPS_PROVIDER); + if (this.cLoc != null) { + this.hasData = true; + } + return this.cLoc; } + /** + * Called when the provider is disabled by the user. + * + * @param provider + */ public void onProviderDisabled(String provider) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The provider " + provider + " is disabled"); - owner.fail(); + this.owner.fail(GeoListener.POSITION_UNAVAILABLE, "GPS provider disabled."); } + /** + * Called when the provider is enabled by the user. + * + * @param provider + */ public void onProviderEnabled(String provider) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The provider "+ provider + " is enabled"); + System.out.println("GpsListener: The provider "+ provider + " is enabled"); } - + /** + * Called when the provider status changes. This method is called when a + * provider is unable to fetch a location or if the provider has recently + * become available after a period of unavailability. + * + * @param provider + * @param status + * @param extras + */ public void onStatusChanged(String provider, int status, Bundle extras) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The status of the provider " + provider + " has changed"); - if(status == 0) - { - Log.d(LOG_TAG, provider + " is OUT OF SERVICE"); - owner.fail(); + System.out.println("GpsListener: The status of the provider " + provider + " has changed"); + if (status == 0) { + System.out.println("GpsListener: " + provider + " is OUT OF SERVICE"); + this.owner.fail(GeoListener.POSITION_UNAVAILABLE, "GPS out of service."); } - else if(status == 1) - { - Log.d(LOG_TAG, provider + " is TEMPORARILY_UNAVAILABLE"); + else if (status == 1) { + System.out.println("GpsListener: " + provider + " is TEMPORARILY_UNAVAILABLE"); } - else - { - Log.d(LOG_TAG, provider + " is Available"); + else { + System.out.println("GpsListener: " + provider + " is Available"); } } - + /** + * Called when the location has changed. + * + * @param location + */ public void onLocationChanged(Location location) { - Log.d(LOG_TAG, "The location has been updated!"); - owner.success(location); + System.out.println("GpsListener: The location has been updated!"); + this.hasData = true; + this.cLoc = location; + this.owner.success(location); } + /** + * Determine if location data is available. + * + * @return + */ public boolean hasLocation() { - return hasData; + return this.hasData; } - public void start(int interval) - { - mLocMan = (LocationManager) mCtx.getSystemService(Context.LOCATION_SERVICE); - mLocMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, 0, this); - cLoc = mLocMan.getLastKnownLocation(LocationManager.GPS_PROVIDER); + /** + * Start requesting location updates. + * + * @param interval + */ + public void start(int interval) { + if (!this.running) { + this.running = true; + this.mLocMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, 0, this); + this.getLocation(); + + // If GPS provider has data, then send now + if (this.hasData) { + this.owner.success(this.cLoc); + } + } } - public void stop() - { - mLocMan.removeUpdates(this); + /** + * Stop receiving location updates. + */ + public void stop() { + if (this.running) { + this.mLocMan.removeUpdates(this); + } + this.running = false; } } diff --git a/framework/src/com/phonegap/NetworkListener.java b/framework/src/com/phonegap/NetworkListener.java old mode 100644 new mode 100755 index 4b0ecda4..0941f08b --- a/framework/src/com/phonegap/NetworkListener.java +++ b/framework/src/com/phonegap/NetworkListener.java @@ -26,82 +26,129 @@ import android.location.Location; import android.location.LocationManager; import android.location.LocationListener; import android.os.Bundle; -import android.util.Log; public class NetworkListener implements LocationListener { - - private Context mCtx; - private Location cLoc; - private LocationManager mLocMan; - private static final String LOG_TAG = "PhoneGap"; - GeoListener owner; - public NetworkListener(Context ctx, int interval, GeoListener m) - { - owner = m; - mCtx = ctx; + private DroidGap mCtx; // DroidGap object + + private LocationManager mLocMan; // Location manager object + private GeoListener owner; // Geolistener object (parent) + private boolean hasData = false; // Flag indicates if location data is available in cLoc + private Location cLoc; // Last recieved location + private boolean running = false; // Flag indicates if listener is running + + /** + * Constructor. + * Automatically starts listening. + * + * @param ctx + * @param interval + * @param m + */ + public NetworkListener(DroidGap ctx, int interval, GeoListener m) { + this.owner = m; + this.mCtx = ctx; + this.mLocMan = (LocationManager) this.mCtx.getSystemService(Context.LOCATION_SERVICE); + this.running = false; this.start(interval); } - public Location getLocation() - { - cLoc = mLocMan.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - return cLoc; + /** + * Get last location. + * + * @return Location object + */ + public Location getLocation() { + this.cLoc = this.mLocMan.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + if (this.cLoc != null) { + this.hasData = true; + } + return this.cLoc; } + /** + * Called when the provider is disabled by the user. + * + * @param provider + */ public void onProviderDisabled(String provider) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The provider " + provider + " is disabled"); + System.out.println("NetworkListener: The provider " + provider + " is disabled"); } - + /** + * Called when the provider is enabled by the user. + * + * @param provider + */ public void onProviderEnabled(String provider) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The provider "+ provider + " is enabled"); + System.out.println("NetworkListener: The provider "+ provider + " is enabled"); } - + /** + * Called when the provider status changes. This method is called when a + * provider is unable to fetch a location or if the provider has recently + * become available after a period of unavailability. + * + * @param provider + * @param status + * @param extras + */ public void onStatusChanged(String provider, int status, Bundle extras) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The status of the provider " + provider + " has changed"); - if(status == 0) - { - Log.d(LOG_TAG, provider + " is OUT OF SERVICE"); + System.out.println("NetworkListener: The status of the provider " + provider + " has changed"); + if (status == 0) { + System.out.println("NetworkListener: " + provider + " is OUT OF SERVICE"); } - else if(status == 1) - { - Log.d(LOG_TAG, provider + " is TEMPORARILY_UNAVAILABLE"); + else if (status == 1) { + System.out.println("NetworkListener: " + provider + " is TEMPORARILY_UNAVAILABLE"); } - else - { - Log.d(LOG_TAG, provider + " is Available"); + else { + System.out.println("NetworkListener: " + provider + " is Available"); } } - - /* - * The GPS is the primary form of Geolocation in PhoneGap. Only fire the success variables if the GPS is down - * for some reason + /** + * Called when the location has changed. + * + * @param location */ public void onLocationChanged(Location location) { - Log.d(LOG_TAG, "The location has been updated!"); - if (!owner.mGps.hasLocation()) - { - owner.success(location); + System.out.println("NetworkListener: The location has been updated!"); + this.hasData = true; + this.cLoc = location; + + // The GPS is the primary form of Geolocation in PhoneGap. + // Only fire the success variables if the GPS is down for some reason. + if (!this.owner.mGps.hasLocation()) { + this.owner.success(location); } - cLoc = location; } - public void start(int interval) - { - mLocMan = (LocationManager) mCtx.getSystemService(Context.LOCATION_SERVICE); - mLocMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, interval, 0, this); - cLoc = mLocMan.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + /** + * Start requesting location updates. + * + * @param interval + */ + public void start(int interval) { + if (!this.running) { + this.running = true; + this.mLocMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, interval, 0, this); + this.getLocation(); + + // If Network provider has data but GPS provider doesn't, then send ours + if (this.hasData && !this.owner.mGps.hasLocation()) { + this.owner.success(this.cLoc); + } + } } - public void stop() - { - mLocMan.removeUpdates(this); + /** + * Stop receiving location updates. + */ + public void stop() { + if (this.running) { + this.mLocMan.removeUpdates(this); + } + this.running = false; } } From 7f7cc1db2a5e15e5fbf2456b1c04114a7eb28cc0 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Thu, 23 Sep 2010 14:34:56 -0500 Subject: [PATCH 20/20] Add geolocation options as defined by W3C spec. --- framework/assets/js/geolocation.js | 38 +++++++++++++++++++++-- framework/src/com/phonegap/GeoBroker.java | 26 ++++++++++------ 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/framework/assets/js/geolocation.js b/framework/assets/js/geolocation.js index 40f06da4..e063f075 100755 --- a/framework/assets/js/geolocation.js +++ b/framework/assets/js/geolocation.js @@ -42,8 +42,22 @@ Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallba } return; } + var maximumAge = 10000; + var enableHighAccuracy = false; + var timeout = 10000; + if (typeof options != "undefined") { + if (typeof options.maximumAge != "undefined") { + maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy != "undefined") { + enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout != "undefined") { + timeout = options.timeout; + } + } navigator._geo.listeners["global"] = {"success" : successCallback, "fail" : errorCallback }; - PhoneGap.execAsync(null, null, "Geolocation", "getCurrentLocation", []); + PhoneGap.execAsync(null, null, "Geolocation", "getCurrentLocation", [enableHighAccuracy, timeout, maximumAge]); } /** @@ -56,15 +70,32 @@ Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallba * @return String The watch id that must be passed to #clearWatch to stop watching. */ Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) { - var frequency = (options != undefined)? options.frequency : 10000; + var maximumAge = 10000; + var enableHighAccuracy = false; + var timeout = 10000; + if (typeof options != "undefined") { + if (typeof options.frequency != "undefined") { + maximumAge = options.frequency; + } + if (typeof options.maximumAge != "undefined") { + maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy != "undefined") { + enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout != "undefined") { + timeout = options.timeout; + } + } var id = PhoneGap.createUUID(); navigator._geo.listeners[id] = {"success" : successCallback, "fail" : errorCallback }; - PhoneGap.execAsync(null, null, "Geolocation", "start", [frequency, id]); + PhoneGap.execAsync(null, null, "Geolocation", "start", [id, enableHighAccuracy, timeout, maximumAge]); return id; }; /* * Native callback when watch position has a new position. + * PRIVATE METHOD * * @param {String} id * @param {Number} lat @@ -98,6 +129,7 @@ Geolocation.prototype.success = function(id, lat, lng, alt, altacc, head, vel, s /** * Native callback when watch position has an error. + * PRIVATE METHOD * * @param {String} id The ID of the watch * @param {Number} code The error code diff --git a/framework/src/com/phonegap/GeoBroker.java b/framework/src/com/phonegap/GeoBroker.java index e799f50c..45281d76 100755 --- a/framework/src/com/phonegap/GeoBroker.java +++ b/framework/src/com/phonegap/GeoBroker.java @@ -67,10 +67,10 @@ public class GeoBroker implements Plugin { try { if (action.equals("getCurrentLocation")) { - this.getCurrentLocation(); + this.getCurrentLocation(args.getBoolean(0), args.getInt(1), args.getInt(2)); } else if (action.equals("start")) { - String s = this.start(args.getInt(0), args.getString(1)); + String s = this.start(args.getString(0), args.getBoolean(1), args.getInt(2), args.getInt(3)); return new PluginResult(status, s); } else if (action.equals("stop")) { @@ -143,36 +143,42 @@ public class GeoBroker implements Plugin { /** * Get current location. * The result is returned to JavaScript via a callback. + * + * @param enableHighAccuracy + * @param timeout + * @param maximumAge */ - public void getCurrentLocation() { + public void getCurrentLocation(boolean enableHighAccuracy, int timeout, int maximumAge) { // Create a geolocation listener just for getCurrentLocation and call it "global" if (this.global == null) { - this.global = new GeoListener("global", this.ctx, 10000, this.webView); + this.global = new GeoListener("global", this.ctx, maximumAge, this.webView); } else { - this.global.start(10000); + this.global.start(maximumAge); } } /** * Start geolocation listener and add to listener list. * - * @param freq Period to retrieve geolocation - * @param key The listener id + * @param key The listener id + * @param enableHighAccuracy + * @param timeout + * @param maximumAge * @return */ - public String start(int freq, String key) { + public String start(String key, boolean enableHighAccuracy, int timeout, int maximumAge) { // Make sure this listener doesn't already exist GeoListener listener = geoListeners.get(key); if (listener == null) { - listener = new GeoListener(key, this.ctx, freq, this.webView); + listener = new GeoListener(key, this.ctx, maximumAge, this.webView); geoListeners.put(key, listener); } // Start it - listener.start(freq); + listener.start(maximumAge); return key; }