From ad8086fab5780cb45111b09365bc7ce3afbbed8c Mon Sep 17 00:00:00 2001 From: Kevin Griffin Date: Tue, 9 Aug 2011 23:18:01 -0400 Subject: [PATCH 01/13] exposing volume control --- framework/assets/js/media.js | 7 ++++ framework/src/com/phonegap/AudioHandler.java | 40 ++++++++++++++------ framework/src/com/phonegap/AudioPlayer.java | 14 ++++++- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/framework/assets/js/media.js b/framework/assets/js/media.js index de955c66..f530025c 100755 --- a/framework/assets/js/media.js +++ b/framework/assets/js/media.js @@ -155,6 +155,13 @@ Media.prototype.release = function() { PhoneGap.exec(null, null, "Media", "release", [this.id]); }; +/** + * Adjust the volume. + */ +Media.prototype.setVolume = function(volume) { + PhoneGap.exec(null, null, "Media", "setVolume", [this.id, volume]); +}; + /** * List of media objects. * PRIVATE diff --git a/framework/src/com/phonegap/AudioHandler.java b/framework/src/com/phonegap/AudioHandler.java index 9673b076..e5409a8e 100755 --- a/framework/src/com/phonegap/AudioHandler.java +++ b/framework/src/com/phonegap/AudioHandler.java @@ -7,17 +7,15 @@ */ package com.phonegap; -import java.util.HashMap; -import java.util.Map.Entry; - +import android.content.Context; +import android.media.AudioManager; +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; import org.json.JSONArray; import org.json.JSONException; -import com.phonegap.api.Plugin; -import com.phonegap.api.PluginResult; - -import android.content.Context; -import android.media.AudioManager; +import java.util.HashMap; +import java.util.Map.Entry; /** * This class called by PhonegapActivity to play and record audio. @@ -71,8 +69,13 @@ public class AudioHandler extends Plugin { } else if (action.equals("stopPlayingAudio")) { this.stopPlayingAudio(args.getString(0)); - } - else if (action.equals("getCurrentPositionAudio")) { + } else if (action.equals("setVolume")) { + try { + this.setVolume(args.getString(0), Float.parseFloat(args.getString(1))); + } catch (NumberFormatException nfe) { + //no-op + } + } else if (action.equals("getCurrentPositionAudio")) { float f = this.getCurrentPositionAudio(args.getString(0)); return new PluginResult(status, f); } @@ -295,5 +298,20 @@ public class AudioHandler extends Plugin { else { return -1; } - } + } + + /** + * Set the volume for an audio device + * + * @param id The id of the audio player + * @param volume Volume to adjust to 0.0f - 1.0f + */ + public void setVolume(String id, float volume) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.setVolume(volume); + } else { + System.out.println("AudioHandler.setVolume() Error: Unknown Audio Player " + id); + } + } } diff --git a/framework/src/com/phonegap/AudioPlayer.java b/framework/src/com/phonegap/AudioPlayer.java index 2d99fdbc..df90795a 100755 --- a/framework/src/com/phonegap/AudioPlayer.java +++ b/framework/src/com/phonegap/AudioPlayer.java @@ -7,8 +7,6 @@ */ package com.phonegap; -import java.io.File; -import java.io.IOException; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; @@ -18,6 +16,9 @@ import android.media.MediaRecorder; import android.os.Environment; import android.util.Log; +import java.io.File; +import java.io.IOException; + /** * This class implements the audio playback and recording capabilities used by PhoneGap. * It is called by the AudioHandler PhoneGap class. @@ -417,4 +418,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.state = state; } + + /** + * Set the volume for audio player + * + * @param volume + */ + public void setVolume(float volume) { + this.mPlayer.setVolume(volume, volume); + } } From 381d1615b40596683b15d81d0e8e209895cb88be Mon Sep 17 00:00:00 2001 From: Kevin Griffin Date: Tue, 9 Aug 2011 23:19:50 -0400 Subject: [PATCH 02/13] formatting --- framework/src/com/phonegap/AudioHandler.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/framework/src/com/phonegap/AudioHandler.java b/framework/src/com/phonegap/AudioHandler.java index e5409a8e..e7f77d90 100755 --- a/framework/src/com/phonegap/AudioHandler.java +++ b/framework/src/com/phonegap/AudioHandler.java @@ -70,12 +70,12 @@ public class AudioHandler extends Plugin { else if (action.equals("stopPlayingAudio")) { this.stopPlayingAudio(args.getString(0)); } else if (action.equals("setVolume")) { - try { - this.setVolume(args.getString(0), Float.parseFloat(args.getString(1))); - } catch (NumberFormatException nfe) { - //no-op - } - } else if (action.equals("getCurrentPositionAudio")) { + try { + this.setVolume(args.getString(0), Float.parseFloat(args.getString(1))); + } catch (NumberFormatException nfe) { + //no-op + } + } else if (action.equals("getCurrentPositionAudio")) { float f = this.getCurrentPositionAudio(args.getString(0)); return new PluginResult(status, f); } From a735a631f6d521be69bb2cec57c253d2097f12c9 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 12 Aug 2011 03:49:51 +0800 Subject: [PATCH 03/13] Formalize document and window event listeners and allow plugins to override eventListeners. --- framework/assets/js/network.js | 4 +- framework/assets/js/phonegap.js.base | 129 ++++++++++++++++++++++- framework/src/com/phonegap/DroidGap.java | 10 +- 3 files changed, 132 insertions(+), 11 deletions(-) mode change 100644 => 100755 framework/src/com/phonegap/DroidGap.java diff --git a/framework/assets/js/network.js b/framework/assets/js/network.js index b17a3434..f71bad50 100755 --- a/framework/assets/js/network.js +++ b/framework/assets/js/network.js @@ -27,7 +27,7 @@ var Connection = function() { // set a timer if still offline at the end of timer send the offline event me._timer = setTimeout(function(){ me.type = type; - PhoneGap.fireEvent('offline'); + PhoneGap.fireDocumentEvent('offline'); me._timer = null; }, me.timeout); } else { @@ -37,7 +37,7 @@ var Connection = function() { me._timer = null; } me.type = type; - PhoneGap.fireEvent('online'); + PhoneGap.fireDocumentEvent('online'); } // should only fire this once diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index f9ec35d2..f171a0cb 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -46,7 +46,9 @@ var PhoneGap = { ready: true, commands: [], timer: null - } + }, + documentEventHandler: {}, // Collection of custom document event handlers + windowEventHandler: {} // Collection of custom window event handlers }; /** @@ -381,6 +383,36 @@ document.addEventListener('DOMContentLoaded', function() { // Intercept calls to document.addEventListener and watch for deviceready PhoneGap.m_document_addEventListener = document.addEventListener; +// Intercept calls to window.addEventListener +PhoneGap.m_window_addEventListener = window.addEventListener; + +/** + * Add a custom window event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addWindowEventHandler = function(event, callback) { + PhoneGap.windowEventHandler[event] = callback; +} + +/** + * Add a custom document event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addDocumentEventHandler = function(event, callback) { + PhoneGap.documentEventHandler[event] = callback; +} + +/** + * Intercept adding document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ document.addEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); if (e === 'deviceready') { @@ -398,15 +430,52 @@ document.addEventListener = function(evt, handler, capture) { if (e === 'backbutton') { PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); } - + + // If subscribing to an event that is handled by a plugin + else if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); } }; +/** + * Intercept adding window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If subscribing to an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_addEventListener.call(window, evt, handler, capture); +}; + // Intercept calls to document.removeEventListener and watch for events that // are generated by PhoneGap native code PhoneGap.m_document_removeEventListener = document.removeEventListener; +// Intercept calls to window.removeEventListener +PhoneGap.m_window_removeEventListener = window.removeEventListener; + +/** + * Intercept removing document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ document.removeEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); @@ -415,18 +484,70 @@ document.removeEventListener = function(evt, handler, capture) { PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); } + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); }; /** - * Method to fire event from native code + * Intercept removing window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture */ -PhoneGap.fireEvent = function(type) { +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_removeEventListener.call(window, evt, handler, capture); +}; + +/** + * Method to fire document event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireDocumentEvent = function(type, data) { var e = document.createEvent('Events'); e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } document.dispatchEvent(e); }; +/** + * Method to fire window event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireWindowEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + window.dispatchEvent(e); +}; + /** * 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. diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java old mode 100644 new mode 100755 index d9ecccd9..f9aca61c --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -1256,7 +1256,7 @@ public class DroidGap extends PhonegapActivity { // If back key is bound, then send event to JavaScript if (this.bound) { - this.appView.loadUrl("javascript:PhoneGap.fireEvent('backbutton');"); + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');"); return true; } @@ -1278,13 +1278,13 @@ public class DroidGap extends PhonegapActivity { // If menu key else if (keyCode == KeyEvent.KEYCODE_MENU) { - this.appView.loadUrl("javascript:PhoneGap.fireEvent('menubutton');"); + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('menubutton');"); return true; } // If search key else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - this.appView.loadUrl("javascript:PhoneGap.fireEvent('searchbutton');"); + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('searchbutton');"); return true; } @@ -1478,13 +1478,13 @@ public class DroidGap extends PhonegapActivity { // gone away. else if (height > oldHeight) { Log.d(LOG_TAG, "Throw hide keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireEvent('hidekeyboard');"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');"); } // If the height as gotten smaller then we will assume the soft keyboard has // been displayed. else if (height < oldHeight) { Log.d(LOG_TAG, "Throw show keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireEvent('showkeyboard');"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');"); } // Update the old height for the next event From 8d46d33675e3b2aa688a98c3a392e505a53a5a3b Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Fri, 12 Aug 2011 03:56:18 +0800 Subject: [PATCH 04/13] Implementation of Battery Event Spec. (http://dev.w3.org/2009/dap/system-info/battery-status.html) --- framework/assets/js/battery.js | 124 +++++++++++++++ .../src/com/phonegap/BatteryListener.java | 145 ++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100755 framework/assets/js/battery.js create mode 100755 framework/src/com/phonegap/BatteryListener.java diff --git a/framework/assets/js/battery.js b/framework/assets/js/battery.js new file mode 100755 index 00000000..883b2de2 --- /dev/null +++ b/framework/assets/js/battery.js @@ -0,0 +1,124 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ + +if (!PhoneGap.hasResource("battery")) { +PhoneGap.addResource("battery"); + +/** + * This class contains information about the current battery status. + * @constructor + */ +var Battery = function() { + this._level = null; + this._isPlugged = null; + this._batteryListener = []; + this._lowListener = []; + this._criticalListener = []; +}; + +/** + * Registers as an event producer for battery events. + * + * @param {Object} eventType + * @param {Object} handler + * @param {Object} add + */ +Battery.prototype.eventHandler = function(eventType, handler, add) { + var me = navigator.battery; + if (add) { + // If there are no current registered event listeners start the battery listener on native side. + if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) { + PhoneGap.exec(me._status, me._error, "Battery", "start", []); + } + + // Register the event listener in the proper array + if (eventType === "batterystatus") { + var pos = me._batteryListener.indexOf(handler); + if (pos === -1) { + me._batteryListener.push(handler); + } + } else if (eventType === "batterylow") { + var pos = me._lowListener.indexOf(handler); + if (pos === -1) { + me._lowListener.push(handler); + } + } else if (eventType === "batterycritical") { + var pos = me._criticalListener.indexOf(handler); + if (pos === -1) { + me._criticalListener.push(handler); + } + } + } else { + // Remove the event listener from the proper array + if (eventType === "batterystatus") { + var pos = me._batteryListener.indexOf(handler); + if (pos > -1) { + me._batteryListener.splice(pos, 1); + } + } else if (eventType === "batterylow") { + var pos = me._lowListener.indexOf(handler); + if (pos > -1) { + me._lowListener.splice(pos, 1); + } + } else if (eventType === "batterycritical") { + var pos = me._criticalListener.indexOf(handler); + if (pos > -1) { + me._criticalListener.splice(pos, 1); + } + } + + // If there are no more registered event listeners stop the battery listener on native side. + if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) { + PhoneGap.exec(null, null, "Battery", "stop", []); + } + } +}; + +/** + * Callback for battery status + * + * @param {Object} info keys: level, isPlugged + */ +Battery.prototype._status = function(info) { + if (info) { + var me = this; + if (me._level != info.level || me._isPlugged != info.isPlugged) { + // Fire batterystatus event + PhoneGap.fireWindowEvent("batterystatus", info); + + // Fire low battery event + if (info.level == 20 || info.level == 5) { + if (info.level == 20) { + PhoneGap.fireWindowEvent("batterylow", info); + } + else { + PhoneGap.fireWindowEvent("batterycritical", info); + } + } + } + me._level = info.level; + me._isPlugged = info.isPlugged; + } +}; + +/** + * Error callback for battery start + */ +Battery.prototype._error = function(e) { + console.log("Error initializing Battery: " + e); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.battery === "undefined") { + navigator.battery = new Battery(); + PhoneGap.addWindowEventHandler("batterystatus", navigator.battery.eventHandler); + PhoneGap.addWindowEventHandler("batterylow", navigator.battery.eventHandler); + PhoneGap.addWindowEventHandler("batterycritical", navigator.battery.eventHandler); + } +}); +} diff --git a/framework/src/com/phonegap/BatteryListener.java b/framework/src/com/phonegap/BatteryListener.java new file mode 100755 index 00000000..ef939db3 --- /dev/null +++ b/framework/src/com/phonegap/BatteryListener.java @@ -0,0 +1,145 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ +package com.phonegap; + +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.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +public class BatteryListener extends Plugin { + + private static final String LOG_TAG = "BatteryManager"; + + BroadcastReceiver receiver; + + private String batteryCallbackId = null; + + /** + * Constructor. + */ + public BatteryListener() { + this.receiver = null; + } + + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackId The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.INVALID_ACTION; + String result = "Unsupported Operation: " + action; + + if (action.equals("start")) { + if (this.batteryCallbackId != null) { + return new PluginResult(PluginResult.Status.ERROR, "Battery listener already running."); + } + this.batteryCallbackId = callbackId; + + // We need to listen to power events to update battery status + IntentFilter intentFilter = new IntentFilter() ; + intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); + if (this.receiver == null) { + this.receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateBatteryInfo(intent); + } + }; + ctx.registerReceiver(this.receiver, intentFilter); + } + + // Don't return any result now, since status results will be sent when events come in from broadcast receiver + PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT); + pluginResult.setKeepCallback(true); + return pluginResult; + } + + else if (action.equals("stop")) { + removeBatteryListener(); + this.sendUpdate(new JSONObject(), false); // release status callback in JS side + this.batteryCallbackId = null; + return new PluginResult(PluginResult.Status.OK); + } + + return new PluginResult(status, result); + } + + /** + * Stop battery receiver. + */ + public void onDestroy() { + removeBatteryListener(); + } + + /** + * Stop the battery receiver and set it to null. + */ + private void removeBatteryListener() { + if (this.receiver != null) { + try { + this.ctx.unregisterReceiver(this.receiver); + this.receiver = null; + } catch (Exception e) { + Log.e(LOG_TAG, "Error unregistering battery receiver: " + e.getMessage(), e); + } + } + } + + /** + * Creates a JSONObject with the current battery information + * + * @param batteryIntent the current battery information + * @return a JSONObject containing the battery status information + */ + private JSONObject getBatteryInfo(Intent batteryIntent) { + JSONObject obj = new JSONObject(); + try { + obj.put("level", batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, 0)); + obj.put("isPlugged", batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_PLUGGED, -1) > 0 ? true : false); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return obj; + } + + /** + * Updates the JavaScript side whenever the battery changes + * + * @param batteryIntent the current battery information + * @return + */ + private void updateBatteryInfo(Intent batteryIntent) { + sendUpdate(this.getBatteryInfo(batteryIntent), true); + } + + /** + * Create a new plugin result and send it back to JavaScript + * + * @param connection the network info to set as navigator.connection + */ + private void sendUpdate(JSONObject info, boolean keepCallback) { + if (this.batteryCallbackId != null) { + PluginResult result = new PluginResult(PluginResult.Status.OK, info); + result.setKeepCallback(keepCallback); + this.success(result, this.batteryCallbackId); + } + } +} From 4ecfbac58677de36119de084f004dc059a8f3231 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Thu, 11 Aug 2011 15:37:11 -0500 Subject: [PATCH 05/13] Include plugin reference and permissions for battery events. --- framework/AndroidManifest.xml | 1 + framework/res/xml/plugins.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/framework/AndroidManifest.xml b/framework/AndroidManifest.xml index c6eeb949..4bae9257 100644 --- a/framework/AndroidManifest.xml +++ b/framework/AndroidManifest.xml @@ -25,6 +25,7 @@ + diff --git a/framework/res/xml/plugins.xml b/framework/res/xml/plugins.xml index 3d8d48d8..dcc229ae 100644 --- a/framework/res/xml/plugins.xml +++ b/framework/res/xml/plugins.xml @@ -16,4 +16,5 @@ + \ No newline at end of file From 361a7aacc5ed1257e11d0c676a940188b7e7922d Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Thu, 11 Aug 2011 16:21:22 -0500 Subject: [PATCH 06/13] Issue #194: Resolve flashes between screen, and enable setting of background color & optional loading dialog. --- framework/assets/js/phonegap.js.base | 3 + framework/src/com/phonegap/DroidGap.java | 111 ++++++++++++++++------- 2 files changed, 82 insertions(+), 32 deletions(-) diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index f171a0cb..f8f64025 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -367,6 +367,9 @@ PhoneGap.Channel.join(function() { // Fire onDeviceReady event once all constructors have run and PhoneGap info has been // received from native side, and any user defined initialization channels. PhoneGap.Channel.join(function() { + // Let native code know we are inited on JS side + prompt("", "gap_init:"); + PhoneGap.onDeviceReady.fire(); // Fire the onresume event, since first one happens before JavaScript is loaded diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index f9aca61c..1ceb52fc 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -95,6 +95,10 @@ import com.phonegap.api.PluginManager; * // (String - default=null) * super.setStringProperty("loadingDialog", "Wait,Loading Demo..."); * + * // Display a native loading dialog when loading sub-pages. Format for value = "Title,Message". + * // (String - default=null) + * super.setStringProperty("loadingPageDialog", "Loading page..."); + * * // Cause all links on web page to be loaded into existing web view, * // instead of being loaded into new browser. (Boolean - default=false) * super.setBooleanProperty("loadInWebView", true); @@ -102,6 +106,10 @@ import com.phonegap.api.PluginManager; * // Load a splash screen image from the resource drawable directory. * // (Integer - default=0) * super.setIntegerProperty("splashscreen", R.drawable.splash); + * + * // Set the background color. + * // (Integer - default=0 or BLACK) + * super.setIntegerProperty("backgroundColor", Color.WHITE); * * // Time in msec to wait before triggering a timeout error when loading * // with super.loadUrl(). (Integer - default=20000) @@ -144,6 +152,10 @@ public class DroidGap extends PhonegapActivity { // Flag indicates that a loadUrl timeout occurred private int loadUrlTimeout = 0; + // Default background color for activity + // (this is not the color for the webview, which is set in HTML) + private int backgroundColor = Color.BLACK; + /* * The variables below are used to cache some of the activity properties. */ @@ -183,7 +195,7 @@ public class DroidGap extends PhonegapActivity { root = new LinearLayoutSoftKeyboardDetect(this, width, height); root.setOrientation(LinearLayout.VERTICAL); - root.setBackgroundColor(Color.BLACK); + root.setBackgroundColor(this.backgroundColor); root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0F)); @@ -297,6 +309,10 @@ public class DroidGap extends PhonegapActivity { this.init(); } + // If backgroundColor + this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); + this.root.setBackgroundColor(this.backgroundColor); + // If spashscreen this.splashscreen = this.getIntegerProperty("splashscreen", 0); if (this.splashscreen != 0) { @@ -712,31 +728,37 @@ public class DroidGap extends PhonegapActivity { public void showWebPage(String url, boolean usePhoneGap, boolean clearPrev, HashMap params) throws android.content.ActivityNotFoundException { Intent intent = null; if (usePhoneGap) { - intent = new Intent().setClass(this, com.phonegap.DroidGap.class); - intent.putExtra("url", url); + try { + intent = new Intent().setClass(this, Class.forName(this.getComponentName().getClassName())); + intent.putExtra("url", url); - // Add parameters - if (params != null) { - java.util.Set> s = params.entrySet(); - java.util.Iterator> it = s.iterator(); - while(it.hasNext()) { - Entry entry = it.next(); - String key = entry.getKey(); - Object value = entry.getValue(); - if (value == null) { + // Add parameters + if (params != null) { + java.util.Set> s = params.entrySet(); + java.util.Iterator> it = s.iterator(); + while(it.hasNext()) { + Entry entry = it.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + } + else if (value.getClass().equals(String.class)) { + intent.putExtra(key, (String)value); + } + else if (value.getClass().equals(Boolean.class)) { + intent.putExtra(key, (Boolean)value); + } + else if (value.getClass().equals(Integer.class)) { + intent.putExtra(key, (Integer)value); + } } - else if (value.getClass().equals(String.class)) { - intent.putExtra(key, (String)value); - } - else if (value.getClass().equals(Boolean.class)) { - intent.putExtra(key, (Boolean)value); - } - else if (value.getClass().equals(Integer.class)) { - intent.putExtra(key, (Integer)value); - } - } - } + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + } } else { intent = new Intent(Intent.ACTION_VIEW); @@ -745,9 +767,9 @@ public class DroidGap extends PhonegapActivity { this.startActivity(intent); // Finish current activity - if (clearPrev) { - this.finish(); - } + if (clearPrev) { + this.finish(); + } } /** @@ -915,6 +937,14 @@ public class DroidGap extends PhonegapActivity { result.confirm(r); } + // PhoneGap JS has initialized, so show webview + // (This solves white flash seen when rendering HTML) + else if (reqOk && defaultValue.equals("gap_init:")) { + appView.setVisibility(View.VISIBLE); + ctx.spinnerStop(); + result.confirm("OK"); + } + // Show dialog else { final JsPromptResult res = result; @@ -1123,7 +1153,11 @@ public class DroidGap extends PhonegapActivity { try { // Init parameters to new DroidGap activity and propagate existing parameters HashMap params = new HashMap(); - params.put("loadingDialog", null); + String loadingPage = this.ctx.getStringProperty("loadingPageDialog", null); + if (loadingPage != null) { + params.put("loadingDialog", loadingPage); + params.put("loadingPageDialog", loadingPage); + } if (this.ctx.loadInWebView) { params.put("loadInWebView", true); } @@ -1133,6 +1167,7 @@ public class DroidGap extends PhonegapActivity { if (errorUrl != null) { params.put("errorUrl", errorUrl); } + params.put("backgroundColor", this.ctx.backgroundColor); this.ctx.showWebPage(url, true, false, params); } catch (android.content.ActivityNotFoundException e) { @@ -1174,11 +1209,23 @@ public class DroidGap extends PhonegapActivity { appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); } - // Make app view visible - appView.setVisibility(View.VISIBLE); - - // Stop "app loading" spinner if showing - this.ctx.spinnerStop(); + // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly + Thread t = new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(2000); + ctx.runOnUiThread(new Runnable() { + public void run() { + appView.setVisibility(View.VISIBLE); + ctx.spinnerStop(); + } + }); + } catch (InterruptedException e) { + } + } + }); + t.start(); + // Clear history, so that previous screen isn't there when Back button is pressed if (this.ctx.clearHistory) { From 80891b8495d0ff114df2b043ebfd98d80cc77c7b Mon Sep 17 00:00:00 2001 From: macdonst Date: Thu, 18 Aug 2011 00:38:00 +0800 Subject: [PATCH 07/13] Fix for Issue #200: NetworkManager missing HSDPA in getType Added HSDPA, HSUPA, HSPA and HSPA+ type detection to NetworkManager. --- .../src/com/phonegap/NetworkManager.java | 358 +++++++++--------- 1 file changed, 183 insertions(+), 175 deletions(-) diff --git a/framework/src/com/phonegap/NetworkManager.java b/framework/src/com/phonegap/NetworkManager.java index 19a8a5c6..c462436b 100755 --- a/framework/src/com/phonegap/NetworkManager.java +++ b/framework/src/com/phonegap/NetworkManager.java @@ -22,201 +22,209 @@ import android.net.NetworkInfo; import android.util.Log; public class NetworkManager extends Plugin { - + public static int NOT_REACHABLE = 0; - public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; - public static int REACHABLE_VIA_WIFI_NETWORK = 2; + public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; + public static int REACHABLE_VIA_WIFI_NETWORK = 2; - public static final String WIFI = "wifi"; - public static final String WIMAX = "wimax"; - // mobile - public static final String MOBILE = "mobile"; - // 2G network types - public static final String GSM = "gsm"; - public static final String GPRS = "gprs"; - public static final String EDGE = "edge"; - // 3G network types - public static final String CDMA = "cdma"; - public static final String UMTS = "umts"; - // 4G network types - public static final String LTE = "lte"; - public static final String UMB = "umb"; - // return types - public static final String TYPE_UNKNOWN = "unknown"; - public static final String TYPE_ETHERNET = "ethernet"; - public static final String TYPE_WIFI = "wifi"; - public static final String TYPE_2G = "2g"; - public static final String TYPE_3G = "3g"; - public static final String TYPE_4G = "4g"; - public static final String TYPE_NONE = "none"; - - private static final String LOG_TAG = "NetworkManager"; + public static final String WIFI = "wifi"; + public static final String WIMAX = "wimax"; + // mobile + public static final String MOBILE = "mobile"; + // 2G network types + public static final String GSM = "gsm"; + public static final String GPRS = "gprs"; + public static final String EDGE = "edge"; + // 3G network types + public static final String CDMA = "cdma"; + public static final String UMTS = "umts"; + public static final String HSPA = "hspa"; + public static final String HSUPA = "hsupa"; + public static final String HSDPA = "hsdpa"; + // 4G network types + public static final String LTE = "lte"; + public static final String UMB = "umb"; + public static final String HSPA_PLUS = "hspa+"; + // return types + public static final String TYPE_UNKNOWN = "unknown"; + public static final String TYPE_ETHERNET = "ethernet"; + public static final String TYPE_WIFI = "wifi"; + public static final String TYPE_2G = "2g"; + public static final String TYPE_3G = "3g"; + public static final String TYPE_4G = "4g"; + public static final String TYPE_NONE = "none"; + + private static final String LOG_TAG = "NetworkManager"; - private String connectionCallbackId; + private String connectionCallbackId; - ConnectivityManager sockMan; - BroadcastReceiver receiver; - - /** - * Constructor. - */ - public NetworkManager() { - this.receiver = null; - } + ConnectivityManager sockMan; + BroadcastReceiver receiver; + + /** + * Constructor. + */ + public NetworkManager() { + this.receiver = null; + } - /** - * 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(PhonegapActivity ctx) { - super.setContext(ctx); - this.sockMan = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); - this.connectionCallbackId = null; - - // We need to listen to connectivity events to update navigator.connection - IntentFilter intentFilter = new IntentFilter() ; - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - if (this.receiver == null) { - this.receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)); - } - }; - ctx.registerReceiver(this.receiver, intentFilter); - } + /** + * 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(PhonegapActivity ctx) { + super.setContext(ctx); + this.sockMan = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + this.connectionCallbackId = null; + + // We need to listen to connectivity events to update navigator.connection + IntentFilter intentFilter = new IntentFilter() ; + intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + if (this.receiver == null) { + this.receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)); + } + }; + ctx.registerReceiver(this.receiver, intentFilter); + } - } - - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackId The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. - */ - public PluginResult execute(String action, JSONArray args, String callbackId) { - PluginResult.Status status = PluginResult.Status.INVALID_ACTION; - String result = "Unsupported Operation: " + action; - - if (action.equals("getConnectionInfo")) { - this.connectionCallbackId = callbackId; - NetworkInfo info = sockMan.getActiveNetworkInfo(); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, this.getConnectionInfo(info)); - pluginResult.setKeepCallback(true); - return pluginResult; - } - - return new PluginResult(status, result); - } + } + + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackId The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.INVALID_ACTION; + String result = "Unsupported Operation: " + action; + + if (action.equals("getConnectionInfo")) { + this.connectionCallbackId = callbackId; + NetworkInfo info = sockMan.getActiveNetworkInfo(); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, this.getConnectionInfo(info)); + pluginResult.setKeepCallback(true); + return pluginResult; + } + + return new PluginResult(status, result); + } - /** - * 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) { - // All methods take a while, so always use async - return false; - } - - /** - * Stop network receiver. - */ - public void onDestroy() { - if (this.receiver != null) { - try { - this.ctx.unregisterReceiver(this.receiver); - } catch (Exception e) { - Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e); - } - } - } + /** + * 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) { + // All methods take a while, so always use async + return false; + } + + /** + * Stop network receiver. + */ + public void onDestroy() { + if (this.receiver != null) { + try { + this.ctx.unregisterReceiver(this.receiver); + } catch (Exception e) { + Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e); + } + } + } //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- - /** - * Updates the JavaScript side whenever the connection changes - * - * @param info the current active network info - * @return - */ - private void updateConnectionInfo(NetworkInfo info) { + /** + * Updates the JavaScript side whenever the connection changes + * + * @param info the current active network info + * @return + */ + private void updateConnectionInfo(NetworkInfo info) { // send update to javascript "navigator.network.connection" sendUpdate(this.getConnectionInfo(info)); - } + } - /** - * Get the latest network connection information - * - * @param info the current active network info - * @return a JSONObject that represents the network info - */ - private String getConnectionInfo(NetworkInfo info) { - String type = TYPE_NONE; - if (info != null) { - // If we are not connected to any network set type to none - if (!info.isConnected()) { - type = TYPE_NONE; - } - else { - type = getType(info); - } - } + /** + * Get the latest network connection information + * + * @param info the current active network info + * @return a JSONObject that represents the network info + */ + private String getConnectionInfo(NetworkInfo info) { + String type = TYPE_NONE; + if (info != null) { + // If we are not connected to any network set type to none + if (!info.isConnected()) { + type = TYPE_NONE; + } + else { + type = getType(info); + } + } return type; - } - - /** - * Create a new plugin result and send it back to JavaScript - * - * @param connection the network info to set as navigator.connection - */ - private void sendUpdate(String type) { - PluginResult result = new PluginResult(PluginResult.Status.OK, type); - result.setKeepCallback(true); - this.success(result, this.connectionCallbackId); - } - - /** - * Determine the type of connection - * - * @param info the network info so we can determine connection type. - * @return the type of mobile network we are on - */ - private String getType(NetworkInfo info) { - if (info != null) { - String type = info.getTypeName(); + } + + /** + * Create a new plugin result and send it back to JavaScript + * + * @param connection the network info to set as navigator.connection + */ + private void sendUpdate(String type) { + PluginResult result = new PluginResult(PluginResult.Status.OK, type); + result.setKeepCallback(true); + this.success(result, this.connectionCallbackId); + } + + /** + * Determine the type of connection + * + * @param info the network info so we can determine connection type. + * @return the type of mobile network we are on + */ + private String getType(NetworkInfo info) { + if (info != null) { + String type = info.getTypeName(); if (type.toLowerCase().equals(WIFI)) { return TYPE_WIFI; } else if (type.toLowerCase().equals(MOBILE)) { - type = info.getSubtypeName(); - if (type.toLowerCase().equals(GSM) || - type.toLowerCase().equals(GPRS) || - type.toLowerCase().equals(EDGE)) { - return TYPE_2G; - } - else if (type.toLowerCase().equals(CDMA) || - type.toLowerCase().equals(UMTS)) { - return TYPE_3G; - } - else if (type.toLowerCase().equals(LTE) || - type.toLowerCase().equals(UMB)) { - return TYPE_4G; - } - } - } - else { - return TYPE_NONE; - } - return TYPE_UNKNOWN; - } + type = info.getSubtypeName(); + if (type.toLowerCase().equals(GSM) || + type.toLowerCase().equals(GPRS) || + type.toLowerCase().equals(EDGE)) { + return TYPE_2G; + } + else if (type.toLowerCase().equals(CDMA) || + type.toLowerCase().equals(UMTS) || + type.toLowerCase().equals(HSUPA) || + type.toLowerCase().equals(HSDPA) || + type.toLowerCase().equals(HSPA)) { + return TYPE_3G; + } + else if (type.toLowerCase().equals(LTE) || + type.toLowerCase().equals(UMB) || + type.toLowerCase().equals(HSPA_PLUS)) { + return TYPE_4G; + } + } + } + else { + return TYPE_NONE; + } + return TYPE_UNKNOWN; + } } From 0e316321f92714de03a2558f181a9c8b1f733c0a Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Sun, 21 Aug 2011 20:50:57 -0500 Subject: [PATCH 08/13] Fix Issue #203: Prompt crashes on Android 3.2 tablet. --- framework/src/com/phonegap/DroidGap.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 1ceb52fc..559be52e 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -914,13 +914,13 @@ public class DroidGap extends PhonegapActivity { } // Polling for JavaScript messages - else if (reqOk && defaultValue.equals("gap_poll:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { String r = callbackServer.getJavascript(); result.confirm(r); } // Calling into CallbackServer - else if (reqOk && defaultValue.equals("gap_callbackServer:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { String r = ""; if (message.equals("usePolling")) { r = ""+callbackServer.usePolling(); @@ -939,7 +939,7 @@ public class DroidGap extends PhonegapActivity { // PhoneGap JS has initialized, so show webview // (This solves white flash seen when rendering HTML) - else if (reqOk && defaultValue.equals("gap_init:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) { appView.setVisibility(View.VISIBLE); ctx.spinnerStop(); result.confirm("OK"); From 8a4737947b95d0304e35fc58a0f4c787262ae3e1 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 22 Aug 2011 09:50:42 -0500 Subject: [PATCH 09/13] Remove old phonegap.js file. --- framework/assets/www/phonegap.js | 2861 ------------------------------ 1 file changed, 2861 deletions(-) delete mode 100644 framework/assets/www/phonegap.js diff --git a/framework/assets/www/phonegap.js b/framework/assets/www/phonegap.js deleted file mode 100644 index 69cf16f7..00000000 --- a/framework/assets/www/phonegap.js +++ /dev/null @@ -1,2861 +0,0 @@ -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - - -/** - * 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 Body onload event. - * onNativeReady Internal event that indicates the PhoneGap native side is ready. - * 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 - * - * 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 = {}; - -/** - * This represents the PhoneGap API itself, and provides a global namespace for accessing - * information about the state of PhoneGap. - * @class - */ -var PhoneGap = { - queue: { - ready: true, - commands: [], - timer: null - } -}; - - -/** - * Custom pub-sub channel that can have functions subscribed to it - */ -PhoneGap.Channel = function(type) -{ - this.type = type; - this.handlers = {}; - this.guid = 0; - this.fired = false; - this.enabled = true; -}; - -/** - * Subscribes the given function to the channel. Any time that - * Channel.fire is called so too will the function. - * Optionally specify an execution context for the function - * and a guid that can be used to stop subscribing to the channel. - * Returns the guid. - */ -PhoneGap.Channel.prototype.subscribe = function(f, c, g) { - // need a function to call - if (f == null) { return; } - - var func = f; - if (typeof c == "object" && f instanceof Function) { func = PhoneGap.close(c, f); } - - g = g || func.observer_guid || f.observer_guid || this.guid++; - func.observer_guid = g; - f.observer_guid = g; - this.handlers[g] = func; - return g; -}; - -/** - * Like subscribe but the function is only called once and then it - * auto-unsubscribes itself. - */ -PhoneGap.Channel.prototype.subscribeOnce = function(f, c) { - var g = null; - var _this = this; - var m = function() { - f.apply(c || null, arguments); - _this.unsubscribe(g); - } - if (this.fired) { - if (typeof c == "object" && f instanceof Function) { f = PhoneGap.close(c, f); } - f.apply(this, this.fireArgs); - } else { - g = this.subscribe(m); - } - return g; -}; - -/** - * Unsubscribes the function with the given guid from the channel. - */ -PhoneGap.Channel.prototype.unsubscribe = function(g) { - if (g instanceof Function) { g = g.observer_guid; } - this.handlers[g] = null; - delete this.handlers[g]; -}; - -/** - * Calls all functions subscribed to this channel. - */ -PhoneGap.Channel.prototype.fire = function(e) { - if (this.enabled) { - var fail = false; - for (var item in this.handlers) { - var handler = this.handlers[item]; - if (handler instanceof Function) { - var rv = (handler.apply(this, arguments)==false); - fail = fail || rv; - } - } - this.fired = true; - this.fireArgs = arguments; - return !fail; - } - return true; -}; - -/** - * Calls the provided function only after all of the channels specified - * have been fired. - */ -PhoneGap.Channel.join = function(h, c) { - var i = c.length; - var f = function() { - if (!(--i)) h(); - } - for (var j=0; j 0) { - s = s + ","; - } - var type = typeof args[i]; - if ((type == "number") || (type == "boolean")) { - s = s + args[i]; - } - else if (args[i] instanceof Array) { - s = s + "[" + args[i] + "]"; - } - else if (args[i] instanceof Object) { - var start = true; - s = s + '{'; - for (var name in args[i]) { - if (!start) { - s = s + ','; - } - s = s + '"' + name + '":'; - var nameType = typeof args[i][name]; - if ((nameType == "number") || (nameType == "boolean")) { - s = s + args[i][name]; - } - else { - s = s + '"' + args[i][name] + '"'; - } - start=false; - } - s = s + '}'; - } - else { - var a = args[i].replace(/\\/g, '\\\\'); - a = a.replace(/"/g, '\\"'); - s = s + '"' + a + '"'; - } - } - s = s + "]"; - return s; - } - else { - return JSON.stringify(args); - } -}; - -/** - * Does a deep clone of the object. - * - * @param obj - * @return - */ -PhoneGap.clone = function(obj) { - if(!obj) { - return obj; - } - - if(obj instanceof Array){ - var retVal = new Array(); - for(var i = 0; i < obj.length; ++i){ - retVal.push(PhoneGap.clone(obj[i])); - } - return retVal; - } - - if (obj instanceof Function) { - return obj; - } - - if(!(obj instanceof Object)){ - return obj; - } - - retVal = new Object(); - for(i in obj){ - if(!(i in retVal) || retVal[i] != obj[i]) { - retVal[i] = PhoneGap.clone(obj[i]); - } - } - return retVal; -}; - -PhoneGap.callbackId = 0; -PhoneGap.callbacks = {}; -PhoneGap.callbackStatus = { - NO_RESULT: 0, - OK: 1, - CLASS_NOT_FOUND_EXCEPTION: 2, - ILLEGAL_ACCESS_EXCEPTION: 3, - INSTANTIATION_EXCEPTION: 4, - MALFORMED_URL_EXCEPTION: 5, - IO_EXCEPTION: 6, - INVALID_ACTION: 7, - JSON_EXCEPTION: 8, - ERROR: 9 - }; - - -/** - * 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.exec = function(success, fail, service, action, args) { - try { - var callbackId = service + PhoneGap.callbackId++; - if (success || fail) { - PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; - } - - // 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) { - eval("var v="+r+";"); - - // If status is OK, then return value back to caller - if (v.status == PhoneGap.callbackStatus.OK) { - - // If there is a success callback, then call it now with returned value - if (success) { - try { - success(v.message); - } - catch (e) { - console.log("Error in success callback: "+callbackId+" = "+e); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } - return v.message; - } - - // If no result - else if (v.status == PhoneGap.callbackStatus.NO_RESULT) { - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } - - // If error, then display error - else { - console.log("Error: Status="+r.status+" Message="+v.message); - - // If there is a fail callback, then call it now with returned value - if (fail) { - try { - fail(v.message); - } - catch (e) { - console.log("Error in error callback: "+callbackId+" = "+e); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } - return null; - } - } - } catch (e) { - console.log("Error: "+e); - } -}; - -/** - * Called by native code when returning successful result from an action. - * - * @param callbackId - * @param args - */ -PhoneGap.callbackSuccess = function(callbackId, args) { - if (PhoneGap.callbacks[callbackId]) { - - // If result is to be sent to callback - if (args.status == PhoneGap.callbackStatus.OK) { - try { - if (PhoneGap.callbacks[callbackId].success) { - PhoneGap.callbacks[callbackId].success(args.message); - } - } - catch (e) { - console.log("Error in success callback: "+callbackId+" = "+e); - } - } - - // Clear callback if not expecting any more results - if (!args.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } -}; - -/** - * 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 { - if (PhoneGap.callbacks[callbackId].fail) { - PhoneGap.callbacks[callbackId].fail(args.message); - } - } - catch (e) { - console.log("Error in error callback: "+callbackId+" = "+e); - } - - // Clear callback if not expecting any more results - if (!args.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } -}; - - -/** - * Internal function used to dispatch the request to PhoneGap. It processes the - * command queue and executes the next command on the list. If one of the - * arguments is a JavaScript object, it will be passed on the QueryString of the - * 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; - - PhoneGap.queue.ready = false; - - var args = PhoneGap.queue.commands.shift(); - if (PhoneGap.queue.commands.length == 0) { - clearInterval(PhoneGap.queue.timer); - PhoneGap.queue.timer = null; - } - - var uri = []; - var dict = null; - for (var i = 1; i < args.length; i++) { - var arg = args[i]; - if (arg == undefined || arg == null) - arg = ''; - if (typeof(arg) == 'object') - dict = arg; - else - uri.push(encodeURIComponent(arg)); - } - var url = "gap://" + args[0] + "/" + uri.join("/"); - if (dict != null) { - var query_args = []; - for (var name in dict) { - if (typeof(name) != 'string') - continue; - query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name])); - } - if (query_args.length > 0) - url += "?" + query_args.join("&"); - } - document.location = url; - -}; - -/** - * 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. - */ -PhoneGap.JSCallback = function() { - var xmlhttp = new XMLHttpRequest(); - - // Callback function when XMLHttpRequest is ready - xmlhttp.onreadystatechange=function(){ - if(xmlhttp.readyState == 4){ - - // If callback has JavaScript statement to execute - if (xmlhttp.status == 200) { - - var msg = xmlhttp.responseText; - setTimeout(function() { - try { - var t = eval(msg); - } - catch (e) { - console.log("JSCallback Error: "+e); - } - }, 1); - setTimeout(PhoneGap.JSCallback, 1); - } - - // If callback ping (used to keep XHR request from timing out) - else if (xmlhttp.status == 404) { - setTimeout(PhoneGap.JSCallback, 10); - } - - // If error, restart callback server - else { - console.log("JSCallback Error: Request failed."); - CallbackServer.restartServer(); - setTimeout(PhoneGap.JSCallback, 100); - } - } - } - - xmlhttp.open("GET", "http://127.0.0.1:"+CallbackServer.getPort()+"/" , true); - xmlhttp.send(); -}; - -/** - * The polling period to use with JSCallbackPolling. - * This can be changed by the application. The default is 50ms. - */ -PhoneGap.JSCallbackPollingPeriod = 50; - -/** - * This is only for Android. - * - * Internal function that uses polling 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. - */ -PhoneGap.JSCallbackPolling = function() { - var msg = CallbackServer.getJavascript(); - if (msg) { - setTimeout(function() { - try { - var t = eval(""+msg); - } - catch (e) { - console.log("JSCallbackPolling Error: "+e); - } - }, 1); - setTimeout(PhoneGap.JSCallbackPolling, 1); - } - else { - setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); - } -}; - -/** - * Create a UUID - * - * @return - */ -PhoneGap.createUUID = function() { - return PhoneGap.UUIDcreatePart(4) + '-' + - PhoneGap.UUIDcreatePart(2) + '-' + - PhoneGap.UUIDcreatePart(2) + '-' + - PhoneGap.UUIDcreatePart(2) + '-' + - PhoneGap.UUIDcreatePart(6); -}; - -PhoneGap.UUIDcreatePart = function(length) { - var uuidpart = ""; - for (var i=0; i frequency + 10 sec - PhoneGap.exec( - function(timeout) { - if (timeout < (frequency + 10000)) { - PhoneGap.exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); - } - }, - function(e) { }, "Accelerometer", "getTimeout", []); - - // Start watch timer - var id = PhoneGap.createUUID(); - navigator.accelerometer.timers[id] = setInterval(function() { - PhoneGap.exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); - }, (frequency ? frequency : 1)); - - return id; -}; - -/** - * Clears the specified accelerometer watch. - * - * @param {String} id The id of the watch returned from #watchAcceleration. - */ -Accelerometer.prototype.clearWatch = function(id) { - - // Stop javascript timer & remove from timer list - if (id && navigator.accelerometer.timers[id] != undefined) { - clearInterval(navigator.accelerometer.timers[id]); - delete navigator.accelerometer.timers[id]; - } -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.accelerometer == "undefined") navigator.accelerometer = new Accelerometer(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class provides access to the device camera. - * - * @constructor - */ -Camera = function() { - this.successCallback = null; - this.errorCallback = null; - this.options = null; -}; - -/** - * Format of image that returned from getPicture. - * - * Example: navigator.camera.getPicture(success, fail, - * { quality: 80, - * destinationType: Camera.DestinationType.DATA_URL, - * 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. - * - * Example: navigator.camera.getPicture(success, fail, - * { quality: 80, - * destinationType: Camera.DestinationType.DATA_URL, - * 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.sourceType", and returns the - * image as defined by the "options.destinationType" option. - - * The defaults are sourceType=CAMERA and destinationType=DATA_URL. - * - * @param {Function} successCallback - * @param {Function} errorCallback - * @param {Object} options - */ -Camera.prototype.getPicture = function(successCallback, errorCallback, options) { - - // successCallback required - if (typeof successCallback != "function") { - console.log("Camera Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Camera Error: errorCallback is not a function"); - return; - } - - this.options = options; - var quality = 80; - if (options.quality) { - quality = this.options.quality; - } - var destinationType = Camera.DestinationType.DATA_URL; - if (this.options.destinationType) { - destinationType = this.options.destinationType; - } - var sourceType = Camera.PictureSourceType.CAMERA; - if (typeof this.options.sourceType == "number") { - sourceType = this.options.sourceType; - } - PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType]); -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.camera == "undefined") navigator.camera = new Camera(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class provides access to device Compass data. - * @constructor - */ -function Compass() { - /** - * The last known Compass position. - */ - this.lastHeading = null; - - /** - * List of compass watch timers - */ - this.timers = {}; -}; - -Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; - -/** - * Asynchronously aquires the current heading. - * - * @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) - * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) - */ -Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { - - // successCallback required - if (typeof successCallback != "function") { - console.log("Compass Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Compass Error: errorCallback is not a function"); - return; - } - - // Get heading - PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); -}; - -/** - * Asynchronously aquires the heading repeatedly at a given interval. - * - * @param {Function} successCallback The function to call each time the heading data is available - * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) - * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. (OPTIONAL) - * @return String The watch id that must be passed to #clearWatch to stop watching. - */ -Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { - - // Default interval (100 msec) - var frequency = (options != undefined) ? options.frequency : 100; - - // successCallback required - if (typeof successCallback != "function") { - console.log("Compass Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Compass Error: errorCallback is not a function"); - return; - } - - // Make sure compass timeout > frequency + 10 sec - PhoneGap.exec( - function(timeout) { - if (timeout < (frequency + 10000)) { - PhoneGap.exec(null, null, "Compass", "setTimeout", [frequency + 10000]); - } - }, - function(e) { }, "Compass", "getTimeout", []); - - // Start watch timer to get headings - var id = PhoneGap.createUUID(); - navigator.compass.timers[id] = setInterval( - function() { - PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); - }, (frequency ? frequency : 1)); - - return id; -}; - - -/** - * Clears the specified heading watch. - * - * @param {String} id The ID of the watch returned from #watchHeading. - */ -Compass.prototype.clearWatch = function(id) { - - // Stop javascript timer & remove from timer list - if (id && navigator.compass.timers[id]) { - clearInterval(navigator.compass.timers[id]); - delete navigator.compass.timers[id]; - } -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.compass == "undefined") navigator.compass = new Compass(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** -* Contains information about a single contact. -* @param {DOMString} id unique identifier -* @param {DOMString} displayName -* @param {ContactName} name -* @param {DOMString} nickname -* @param {ContactField[]} phoneNumbers array of phone numbers -* @param {ContactField[]} emails array of email addresses -* @param {ContactAddress[]} addresses array of addresses -* @param {ContactField[]} ims instant messaging user ids -* @param {ContactOrganization[]} organizations -* @param {DOMString} published date contact was first created -* @param {DOMString} updated date contact was last updated -* @param {DOMString} birthday contact's birthday -* @param (DOMString} anniversary contact's anniversary -* @param {DOMString} gender contact's gender -* @param {DOMString} note user notes about contact -* @param {DOMString} preferredUsername -* @param {ContactField[]} photos -* @param {ContactField[]} tags -* @param {ContactField[]} relationships -* @param {ContactField[]} urls contact's web sites -* @param {ContactAccounts[]} accounts contact's online accounts -* @param {DOMString} utcOffset UTC time zone offset -* @param {DOMString} connected -*/ -var Contact = function(id, displayName, name, nickname, phoneNumbers, emails, addresses, - ims, organizations, published, updated, birthday, anniversary, gender, note, - preferredUsername, photos, tags, relationships, urls, accounts, utcOffset, connected) { - this.id = id || null; - this.displayName = displayName || null; - this.name = name || null; // ContactName - this.nickname = nickname || null; - this.phoneNumbers = phoneNumbers || null; // ContactField[] - this.emails = emails || null; // ContactField[] - this.addresses = addresses || null; // ContactAddress[] - this.ims = ims || null; // ContactField[] - this.organizations = organizations || null; // ContactOrganization[] - this.published = published || null; - this.updated = updated || null; - this.birthday = birthday || null; - this.anniversary = anniversary || null; - this.gender = gender || null; - this.note = note || null; - this.preferredUsername = preferredUsername || null; - this.photos = photos || null; // ContactField[] - this.tags = tags || null; // ContactField[] - this.relationships = relationships || null; // ContactField[] - this.urls = urls || null; // ContactField[] - this.accounts = accounts || null; // ContactAccount[] - this.utcOffset = utcOffset || null; - this.connected = connected || null; -}; - -/** -* Removes contact from device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.remove = function(successCB, errorCB) { - if (this.id == null) { - var errorObj = new ContactError(); - errorObj.code = ContactError.NOT_FOUND_ERROR; - errorCB(errorObj); - } - - PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); -}; - -/** -* Creates a deep copy of this Contact. -* With the contact ID set to null. -* @return copy of this Contact -*/ -Contact.prototype.clone = function() { - var clonedContact = PhoneGap.clone(this); - clonedContact.id = null; - return clonedContact; -}; - -/** -* Persists contact to device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.save = function(successCB, errorCB) { -}; - -/** -* Contact name. -* @param formatted -* @param familyName -* @param givenName -* @param middle -* @param prefix -* @param suffix -*/ -var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { - this.formatted = formatted || null; - this.familyName = familyName || null; - this.givenName = givenName || null; - this.middleName = middle || null; - this.honorificPrefix = prefix || null; - this.honorificSuffix = suffix || null; -}; - -/** -* Generic contact field. -* @param type -* @param value -* @param primary -*/ -var ContactField = function(type, value, primary) { - this.type = type || null; - this.value = value || null; - this.primary = primary || null; -}; - -/** -* Contact address. -* @param formatted -* @param streetAddress -* @param locality -* @param region -* @param postalCode -* @param country -*/ -var ContactAddress = function(formatted, streetAddress, locality, region, postalCode, country) { - this.formatted = formatted || null; - this.streetAddress = streetAddress || null; - this.locality = locality || null; - this.region = region || null; - this.postalCode = postalCode || null; - this.country = country || null; -}; - -/** -* Contact organization. -* @param name -* @param dept -* @param title -* @param startDate -* @param endDate -* @param location -* @param desc -*/ -var ContactOrganization = function(name, dept, title, startDate, endDate, location, desc) { - this.name = name || null; - this.department = dept || null; - this.title = title || null; - this.startDate = startDate || null; - this.endDate = endDate || null; - this.location = location || null; - this.description = desc || null; -}; - -/** -* Contact account. -* @param domain -* @param username -* @param userid -*/ -var ContactAccount = function(domain, username, userid) { - this.domain = domain || null; - this.username = username || null; - this.userid = userid || null; -} - -/** -* Represents a group of Contacts. -*/ -var Contacts = function() { - this.inProgress = false; - this.records = new Array(); -} -/** -* Returns an array of Contacts matching the search criteria. -* @param fields that should be searched -* @param successCB success callback -* @param errorCB error callback -* @param {ContactFindOptions} options that can be applied to contact searching -* @return array of Contacts matching search criteria -*/ -Contacts.prototype.find = function(fields, successCB, errorCB, options) { - PhoneGap.exec(successCB, errorCB, "Contacts", "search", [fields, options]); -}; - -/** -* This function creates a new contact, but it does not persist the contact -* to device storage. To persist the contact to device storage, invoke -* contact.save(). -* @param properties an object who's properties will be examined to create a new Contact -* @returns new Contact object -*/ -Contacts.prototype.create = function(properties) { - var contact = new Contact(); - for (i in properties) { - if (contact[i]!='undefined') { - contact[i]=properties[i]; - } - } - return contact; -}; - -/** - * ContactFindOptions. - * @param filter used to match contacts against - * @param multiple boolean used to determine if more than one contact should be returned - * @param limit maximum number of results to return from the contacts search - * @param updatedSince return only contact records that have been updated on or after the given time - */ -var ContactFindOptions = function(filter, multiple, limit, updatedSince) { - this.filter = filter || ''; - this.multiple = multiple || false; - this.limit = limit || 1; - this.updatedSince = updatedSince || ''; -}; - -/** - * ContactError. - * An error code assigned by an implementation when an error has occurred - */ -var ContactError = function() { - this.code=null; -}; - -/** - * Error codes - */ -ContactError.UNKNOWN_ERROR = 0; -ContactError.INVALID_ARGUMENT_ERROR = 1; -ContactError.NOT_FOUND_ERROR = 2; -ContactError.TIMEOUT_ERROR = 3; -ContactError.PENDING_OPERATION_ERROR = 4; -ContactError.IO_ERROR = 5; -ContactError.NOT_SUPPORTED_ERROR = 6; -ContactError.PERMISSION_DENIED_ERROR = 20; - -/** - * Add the contact interface into the browser. - */ -PhoneGap.addConstructor(function() { - if(typeof navigator.service == "undefined") navigator.service = new Object(); - if(typeof navigator.service.contacts == "undefined") navigator.service.contacts = new Contacts(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -// TODO: Needs to be commented - -var Crypto = function() { -}; - -Crypto.prototype.encrypt = function(seed, string, callback) { - this.encryptWin = callback; - PhoneGap.exec(null, null, "Crypto", "encrypt", [seed, string]); -}; - -Crypto.prototype.decrypt = function(seed, string, callback) { - this.decryptWin = callback; - PhoneGap.exec(null, null, "Crypto", "decrypt", [seed, string]); -}; - -Crypto.prototype.gotCryptedString = function(string) { - this.encryptWin(string); -}; - -Crypto.prototype.getPlainString = function(string) { - this.decryptWin(string); -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.Crypto == "undefined") navigator.Crypto = new Crypto(); -}); - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * 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.uuid = null; - this.phonegap = null; - - var me = this; - 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); - }); -} - -/** - * 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.exec(successCallback, errorCallback, "Device", "getDeviceInfo", []); -}; - -/* - * This is only for Android. - * - * You must explicitly override the back button. - */ -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(); -} - -/* - * This is only for Android. - * - * This terminates the activity! - */ -Device.prototype.exitApp = function() { - BackButton.exitApp(); -} - -PhoneGap.addConstructor(function() { - navigator.device = window.device = new Device(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * 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; -}; - -/** - * 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.exec(successCallback, errorCallback, "File", "testSaveLocationExists", []); -}; - -FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "testFileExists", [fileName]); -}; - -FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]); -}; - -FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "createDirectory", [dirName]); -}; - -FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "deleteDirectory", [dirName]); -}; - -FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "deleteFile", [fileName]); -}; - -FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "getFreeDiskSpace", []); -}; - -FileMgr.prototype.writeAsText = function(fileName, data, append, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "writeAsText", [fileName, data, append]); -}; - -FileMgr.prototype.readAsText = function(fileName, encoding, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "readAsText", [fileName, encoding]); -}; - -FileMgr.prototype.readAsDataURL = function(fileName, successCallback, errorCallback) { - PhoneGap.exec(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); - } - } - ); -}; - - -/** - * Read file and return data as a base64 encoded data url. - * A data url is of the form: - * data:[][;base64], - * - * @param file The name of the file - */ -FileReader.prototype.readAsDataURL = function(file) { - 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); - } - - var me = this; - - // 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. - * - * @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 -//----------------------------------------------------------------------------- - -/** - * 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.readyState = 0; // EMPTY - - this.result = null; - - // Error - this.error = null; - - // Event handlers - this.onwritestart = null; // When writing starts - this.onprogress = null; // While writing the file, and reporting partial file data - this.onwrite = null; // When the write has successfully completed. - this.onwriteend = null; // When the request has completed (either in success or failure). - this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. - this.onerror = null; // When the write has failed (see errors). -}; - -// States -FileWriter.INIT = 0; -FileWriter.WRITING = 1; -FileWriter.DONE = 2; - -/** - * Abort writing file. - */ -FileWriter.prototype.abort = function() { - this.readyState = FileWriter.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? -}; - -FileWriter.prototype.writeAsText = function(file, text, bAppend) { - if (bAppend != true) { - bAppend = false; // for null values - } - - this.fileName = file; - - // WRITING state - this.readyState = FileWriter.WRITING; - - var me = this; - - // Write file - navigator.fileMgr.writeAsText(file, text, bAppend, - - // Success callback - function(r) { - - // If DONE (cancelled), then don't do anything - if (me.readyState == FileWriter.DONE) { - return; - } - - // Save result - me.result = r; - - // DONE state - me.readyState = FileWriter.DONE; - - // If onwrite callback - if (typeof me.onwrite == "function") { - var evt = File._createEvent("write", me); - me.onwrite(evt); - } - - // If onwriteend callback - if (typeof me.onwriteend == "function") { - var evt = File._createEvent("writeend", me); - me.onwriteend(evt); - } - }, - - // Error callback - function(e) { - - // If DONE (cancelled), then don't do anything - if (me.readyState == FileWriter.DONE) { - return; - } - - // Save error - me.error = e; - - // DONE state - me.readyState = FileWriter.DONE; - - // If onerror callback - if (typeof me.onerror == "function") { - var evt = File._createEvent("error", me); - me.onerror(evt); - } - - // If onwriteend callback - if (typeof me.onwriteend == "function") { - var evt = File._createEvent("writeend", me); - me.onwriteend(evt); - } - } - ); - -}; - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class provides access to device GPS data. - * @constructor - */ -function Geolocation() { - - // The last known GPS position. - this.lastPosition = null; - - // Geolocation listeners - this.listeners = {}; -}; - -/** - * Position error object - * - * @param code - * @param message - */ -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; - } - 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.exec(null, null, "Geolocation", "getCurrentLocation", [enableHighAccuracy, timeout, maximumAge]); -} - -/** - * 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 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.exec(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 - * @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. - * PRIVATE METHOD - * - * @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.exec(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() { - 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; - } -}); - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -function KeyEvent() -{ -} - -KeyEvent.prototype.backTrigger = function() -{ - var e = document.createEvent('Events'); - e.initEvent('backKeyDown'); - document.dispatchEvent(e); -} - -if (document.keyEvent == null || typeof document.keyEvent == 'undefined') -{ - window.keyEvent = document.keyEvent = new KeyEvent(); -} -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * List of media objects. - * PRIVATE - */ -PhoneGap.mediaObjects = {}; - -/** - * Object that receives native callbacks. - * PRIVATE - */ -PhoneGap.Media = function() {}; - -/** - * Get the media object. - * PRIVATE - * - * @param id The media object id (string) - */ -PhoneGap.Media.getMediaObject = function(id) { - return PhoneGap.mediaObjects[id]; -}; - -/** - * Audio has status update. - * PRIVATE - * - * @param id The media object id (string) - * @param status The status code (int) - * @param msg The status message (string) - */ -PhoneGap.Media.onStatus = function(id, msg, value) { - var media = PhoneGap.mediaObjects[id]; - - // If state update - if (msg == Media.MEDIA_STATE) { - if (value == Media.MEDIA_STOPPED) { - if (media.successCallback) { - media.successCallback(); - } - } - if (media.statusCallback) { - media.statusCallback(value); - } - } - else if (msg == Media.MEDIA_DURATION) { - media._duration = value; - } - else if (msg == Media.MEDIA_ERROR) { - if (media.errorCallback) { - media.errorCallback(value); - } - } -}; - -/** - * This class provides access to the device media, interfaces to both sound and video - * - * @param src The file name or url to play - * @param successCallback The callback to be called when the file is done playing or recording. - * successCallback() - OPTIONAL - * @param errorCallback The callback to be called if there is an error. - * errorCallback(int errorCode) - OPTIONAL - * @param statusCallback The callback to be called when media status has changed. - * statusCallback(int statusCode) - OPTIONAL - * @param positionCallback The callback to be called when media position has changed. - * positionCallback(long position) - OPTIONAL - */ -Media = function(src, successCallback, errorCallback, statusCallback, positionCallback) { - - // successCallback optional - if (successCallback && (typeof successCallback != "function")) { - console.log("Media Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Media Error: errorCallback is not a function"); - return; - } - - // statusCallback optional - if (statusCallback && (typeof statusCallback != "function")) { - console.log("Media Error: statusCallback is not a function"); - return; - } - - // statusCallback optional - if (positionCallback && (typeof positionCallback != "function")) { - console.log("Media Error: positionCallback is not a function"); - return; - } - - this.id = PhoneGap.createUUID(); - PhoneGap.mediaObjects[this.id] = this; - this.src = src; - this.successCallback = successCallback; - this.errorCallback = errorCallback; - this.statusCallback = statusCallback; - this.positionCallback = positionCallback; - this._duration = -1; - this._position = -1; -}; - -// Media messages -Media.MEDIA_STATE = 1; -Media.MEDIA_DURATION = 2; -Media.MEDIA_ERROR = 9; - -// Media states -Media.MEDIA_NONE = 0; -Media.MEDIA_STARTING = 1; -Media.MEDIA_RUNNING = 2; -Media.MEDIA_PAUSED = 3; -Media.MEDIA_STOPPED = 4; -Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; - -// TODO: Will MediaError be used? -/** - * This class contains information about any Media errors. - * @constructor - */ -function MediaError() { - this.code = null, - this.message = ""; -}; - -MediaError.MEDIA_ERR_ABORTED = 1; -MediaError.MEDIA_ERR_NETWORK = 2; -MediaError.MEDIA_ERR_DECODE = 3; -MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; - -/** - * Start or resume playing audio file. - */ -Media.prototype.play = function() { - PhoneGap.exec(null, null, "Media", "startPlayingAudio", [this.id, this.src]); -}; - -/** - * Stop playing audio file. - */ -Media.prototype.stop = function() { - return PhoneGap.exec(null, null, "Media", "stopPlayingAudio", [this.id]); -}; - -/** - * Pause playing audio file. - */ -Media.prototype.pause = function() { - PhoneGap.exec(null, null, "Media", "pausePlayingAudio", [this.id]); -}; - -/** - * Get duration of an audio file. - * The duration is only set for audio that is playing, paused or stopped. - * - * @return duration or -1 if not known. - */ -Media.prototype.getDuration = function() { - return this._duration; -}; - -/** - * Get position of audio. - * - * @return - */ -Media.prototype.getCurrentPosition = function(success, fail) { - PhoneGap.exec(success, fail, "Media", "getCurrentPositionAudio", [this.id]); -}; - -/** - * Start recording audio file. - */ -Media.prototype.startRecord = function() { - PhoneGap.exec(null, null, "Media", "startRecordingAudio", [this.id, this.src]); -}; - -/** - * Stop recording audio file. - */ -Media.prototype.stopRecord = function() { - PhoneGap.exec(null, null, "Media", "stopRecordingAudio", [this.id]); -}; - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class contains information about any NetworkStatus. - * @constructor - */ -function NetworkStatus() { - //this.code = null; - //this.message = ""; -}; - -NetworkStatus.NOT_REACHABLE = 0; -NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; -NetworkStatus.REACHABLE_VIA_WIFI_NETWORK = 2; - -/** - * This class provides access to device Network data (reachability). - * @constructor - */ -function Network() { - /** - * The last known Network status. - * { hostName: string, ipAddress: string, - remoteHostStatus: int(0/1/2), internetConnectionStatus: int(0/1/2), localWiFiConnectionStatus: int (0/2) } - */ - this.lastReachability = null; -}; - -/** - * Called by the geolocation framework when the reachability status has changed. - * @param {Reachibility} reachability The current reachability status. - */ -// TODO: Callback from native code not implemented for Android -Network.prototype.updateReachability = function(reachability) { - this.lastReachability = reachability; -}; - -/** - * Determine if a URI is reachable over the network. - - * @param {Object} uri - * @param {Function} callback - * @param {Object} options (isIpAddress:boolean) - */ -Network.prototype.isReachable = function(uri, callback, options) { - var isIpAddress = false; - if (options && options.isIpAddress) { - isIpAddress = options.isIpAddress; - } - PhoneGap.exec(callback, null, "Network Status", "isReachable", [uri, isIpAddress]); -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.network == "undefined") navigator.network = new Network(); -}); - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class provides access to notifications on the device. - */ -function Notification() { -} - -/** - * Open a native alert dialog, with a customizable title and button text. - * - * @param {String} message Message to print in the body of the alert - * @param {Function} completeCallback The callback that is called when user clicks on a button. - * @param {String} title Title of the alert dialog (default: Alert) - * @param {String} buttonLabel Label of the close button (default: OK) - */ -Notification.prototype.alert = function(message, completeCallback, title, buttonLabel) { - var _title = (title || "Alert"); - var _buttonLabel = (buttonLabel || "OK"); - PhoneGap.exec(completeCallback, null, "Notification", "alert", [message,_title,_buttonLabel]); -}; - -/** - * Open a native confirm dialog, with a customizable title and button text. - * The result that the user selects is returned to the result callback. - * - * @param {String} message Message to print in the body of the alert - * @param {Function} resultCallback The callback that is called when user clicks on a button. - * @param {String} title Title of the alert dialog (default: Confirm) - * @param {String} buttonLabels Comma separated list of the labels of the buttons (default: 'OK,Cancel') - */ -Notification.prototype.confirm = function(message, resultCallback, title, buttonLabels) { - var _title = (title || "Confirm"); - var _buttonLabels = (buttonLabels || "OK,Cancel"); - PhoneGap.exec(resultCallback, null, "Notification", "confirm", [message,_title,_buttonLabels]); -}; - -/** - * Start spinning the activity indicator on the statusbar - */ -Notification.prototype.activityStart = function() { - PhoneGap.exec(null, null, "Notification", "activityStart", ["Busy","Please wait..."]); -}; - -/** - * Stop spinning the activity indicator on the statusbar, if it's currently spinning - */ -Notification.prototype.activityStop = function() { - PhoneGap.exec(null, null, "Notification", "activityStop", []); -}; - -/** - * Display a progress dialog with progress bar that goes from 0 to 100. - * - * @param {String} title Title of the progress dialog. - * @param {String} message Message to display in the dialog. - */ -Notification.prototype.progressStart = function(title, message) { - PhoneGap.exec(null, null, "Notification", "progressStart", [title, message]); -}; - -/** - * Set the progress dialog value. - * - * @param {Number} value 0-100 - */ -Notification.prototype.progressValue = function(value) { - PhoneGap.exec(null, null, "Notification", "progressValue", [value]); -}; - -/** - * Close the progress dialog. - */ -Notification.prototype.progressStop = function() { - PhoneGap.exec(null, null, "Notification", "progressStop", []); -}; - -/** - * Causes the device to blink a status LED. - * - * @param {Integer} count The number of blinks. - * @param {String} colour The colour of the light. - */ -Notification.prototype.blink = function(count, colour) { - // NOT IMPLEMENTED -}; - -/** - * Causes the device to vibrate. - * - * @param {Integer} mills The number of milliseconds to vibrate for. - */ -Notification.prototype.vibrate = function(mills) { - PhoneGap.exec(null, null, "Notification", "vibrate", [mills]); -}; - -/** - * Causes the device to beep. - * On Android, the default notification ringtone is played "count" times. - * - * @param {Integer} count The number of beeps. - */ -Notification.prototype.beep = function(count) { - PhoneGap.exec(null, null, "Notification", "beep", [count]); -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.notification == "undefined") navigator.notification = new Notification(); -}); - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class contains position information. - * @param {Object} lat - * @param {Object} lng - * @param {Object} acc - * @param {Object} alt - * @param {Object} altacc - * @param {Object} head - * @param {Object} vel - * @constructor - */ -function Position(coords, timestamp) { - this.coords = coords; - this.timestamp = new Date().getTime(); -} - -function Coordinates(lat, lng, alt, acc, head, vel, altacc) { - /** - * The latitude of the position. - */ - this.latitude = lat; - /** - * The longitude of the position, - */ - this.longitude = lng; - /** - * The accuracy of the position. - */ - this.accuracy = acc; - /** - * The altitude of the position. - */ - this.altitude = alt; - /** - * The direction the device is moving at the position. - */ - this.heading = head; - /** - * The velocity with which the device is moving at the position. - */ - this.speed = vel; - /** - * The altitude accuracy of the position. - */ - this.altitudeAccuracy = (altacc != 'undefined') ? altacc : null; -} - -/** - * This class specifies the options for requesting position data. - * @constructor - */ -function PositionOptions() { - /** - * Specifies the desired position accuracy. - */ - this.enableHighAccuracy = true; - /** - * The timeout after which if position data cannot be obtained the errorCallback - * is called. - */ - this.timeout = 10000; -} - -/** - * This class contains information about any GSP errors. - * @constructor - */ -function PositionError() { - this.code = null; - this.message = ""; -} - -PositionError.UNKNOWN_ERROR = 0; -PositionError.PERMISSION_DENIED = 1; -PositionError.POSITION_UNAVAILABLE = 2; -PositionError.TIMEOUT = 3; -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -PhoneGap.addConstructor(function() { - if (typeof navigator.splashScreen == "undefined") { - navigator.splashScreen = SplashScreen; // SplashScreen object come from native side through addJavaScriptInterface - } -});/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/* - * This is purely for the Android 1.5/1.6 HTML 5 Storage - * I was hoping that Android 2.0 would deprecate this, but given the fact that - * most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required - */ - -/** - * Storage object that is called by native code when performing queries. - * PRIVATE METHOD - */ -var DroidDB = function() { - this.queryQueue = {}; -}; - -/** - * Callback from native code when result from a query is available. - * PRIVATE METHOD - * - * @param rawdata JSON string of the row data - * @param id Query id - */ -DroidDB.prototype.addResult = function(rawdata, id) { - try { - eval("var data = " + rawdata + ";"); - var query = this.queryQueue[id]; - query.resultSet.push(data); - } catch (e) { - console.log("DroidDB.addResult(): Error="+e); - } -}; - -/** - * Callback from native code when query is complete. - * PRIVATE METHOD - * - * @param id Query id - */ -DroidDB.prototype.completeQuery = function(id) { - var query = this.queryQueue[id]; - if (query) { - try { - delete this.queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - - // Save query results - var r = new DroidDB_Result(); - r.rows.resultSet = query.resultSet; - r.rows.length = query.resultSet.length; - try { - if (typeof query.successCallback == 'function') { - query.successCallback(query.tx, r); - } - } catch (ex) { - console.log("executeSql error calling user success callback: "+ex); - } - - tx.queryComplete(id); - } - } catch (e) { - console.log("executeSql error: "+e); - } - } -}; - -/** - * Callback from native code when query fails - * PRIVATE METHOD - * - * @param reason Error message - * @param id Query id - */ -DroidDB.prototype.fail = function(reason, id) { - var query = this.queryQueue[id]; - if (query) { - try { - delete this.queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - tx.queryList = {}; - - try { - if (typeof query.errorCallback == 'function') { - query.errorCallback(query.tx, reason); - } - } catch (ex) { - console.log("executeSql error calling user error callback: "+ex); - } - - tx.queryFailed(id, reason); - } - - } catch (e) { - console.log("executeSql error: "+e); - } - } -}; - -var DatabaseShell = function() { -}; - -/** - * Start a transaction. - * Does not support rollback in event of failure. - * - * @param process {Function} The transaction function - * @param successCallback {Function} - * @param errorCallback {Function} - */ -DatabaseShell.prototype.transaction = function(process, successCallback, errorCallback) { - var tx = new DroidDB_Tx(); - tx.successCallback = successCallback; - tx.errorCallback = errorCallback; - try { - process(tx); - } catch (e) { - console.log("Transaction error: "+e); - if (tx.errorCallback) { - try { - tx.errorCallback(e); - } catch (ex) { - console.log("Transaction error calling user error callback: "+e); - } - } - } -}; - -/** - * Transaction object - * PRIVATE METHOD - */ -var DroidDB_Tx = function() { - - // Set the id of the transaction - this.id = PhoneGap.createUUID(); - - // Callbacks - this.successCallback = null; - this.errorCallback = null; - - // Query list - this.queryList = {}; -}; - -/** - * Mark query in transaction as complete. - * If all queries are complete, call the user's transaction success callback. - * - * @param id Query id - */ -DroidDB_Tx.prototype.queryComplete = function(id) { - delete this.queryList[id]; - - // If no more outstanding queries, then fire transaction success - if (this.successCallback) { - var count = 0; - for (var i in this.queryList) { - count++; - } - if (count == 0) { - try { - this.successCallback(); - } catch(e) { - console.log("Transaction error calling user success callback: " + e); - } - } - } -}; - -/** - * Mark query in transaction as failed. - * - * @param id Query id - * @param reason Error message - */ -DroidDB_Tx.prototype.queryFailed = function(id, reason) { - - // The sql queries in this transaction have already been run, since - // we really don't have a real transaction implemented in native code. - // However, the user callbacks for the remaining sql queries in transaction - // will not be called. - this.queryList = {}; - - if (this.errorCallback) { - try { - this.errorCallback(reason); - } catch(e) { - console.log("Transaction error calling user error callback: " + e); - } - } -}; - -/** - * SQL query object - * PRIVATE METHOD - * - * @param tx The transaction object that this query belongs to - */ -var DroidDB_Query = function(tx) { - - // Set the id of the query - this.id = PhoneGap.createUUID(); - - // Add this query to the queue - droiddb.queryQueue[this.id] = this; - - // Init result - this.resultSet = []; - - // Set transaction that this query belongs to - this.tx = tx; - - // Add this query to transaction list - this.tx.queryList[this.id] = this; - - // Callbacks - this.successCallback = null; - this.errorCallback = null; - -} - -/** - * Execute SQL statement - * - * @param sql SQL statement to execute - * @param params Statement parameters - * @param successCallback Success callback - * @param errorCallback Error callback - */ -DroidDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) { - - // Init params array - if (typeof params == 'undefined') { - params = []; - } - - // Create query and add to queue - var query = new DroidDB_Query(this); - droiddb.queryQueue[query.id] = query; - - // Save callbacks - query.successCallback = successCallback; - query.errorCallback = errorCallback; - - // Call native code - PhoneGap.exec(null, null, "Storage", "executeSql", [sql, params, query.id]); -}; - -/** - * SQL result set that is returned to user. - * PRIVATE METHOD - */ -DroidDB_Result = function() { - this.rows = new DroidDB_Rows(); -}; - -/** - * SQL result set object - * PRIVATE METHOD - */ -DroidDB_Rows = function() { - this.resultSet = []; // results array - this.length = 0; // number of rows -}; - -/** - * Get item from SQL result set - * - * @param row The row number to return - * @return The row object - */ -DroidDB_Rows.prototype.item = function(row) { - return this.resultSet[row]; -}; - -/** - * Open database - * - * @param name Database name - * @param version Database version - * @param display_name Database display name - * @param size Database size in bytes - * @return Database object - */ -DroidDB_openDatabase = function(name, version, display_name, size) { - PhoneGap.exec(null, null, "Storage", "openDatabase", [name, version, display_name, size]); - var db = new DatabaseShell(); - return db; -}; - -PhoneGap.addConstructor(function() { - if (typeof window.openDatabase == "undefined") { - navigator.openDatabase = window.openDatabase = DroidDB_openDatabase; - window.droiddb = new DroidDB(); - } -}); From a9c34e65fbdcfa73e8f560d197093867b26af637 Mon Sep 17 00:00:00 2001 From: macdonst Date: Tue, 23 Aug 2011 01:38:00 +0800 Subject: [PATCH 10/13] Fix for issue #141: EXIF data stripped from captured photos in android In order to fix this issue I needed to read the EXIF data. Save it to a temporary object then after the bitmap is compressed I open the file and write the saved EXIF data. Supports the following EXIF fields if they are set in your image: APERTURE DATETIME EXPOSURE_TIME FLASH FOCAL_LENGTH GPS_ALTITUDE GPS_ALTITUDE_REF GPS_DATESTAMP GPS_LATITUDE GPS_LATITUDE_REF GPS_LONGITUDE GPS_LONGITUDE_REF GPS_PROCESSING_METHOD GPS_TIMESTAMP ISO MAKE MODEL ORIENTATION WHITE_BALANCE --- .../src/com/phonegap/CameraLauncher.java | 531 ++--- framework/src/com/phonegap/Capture.java | 564 +++-- framework/src/com/phonegap/DroidGap.java | 1824 ++++++++--------- framework/src/com/phonegap/ExifHelper.java | 153 ++ framework/src/com/phonegap/FileUtils.java | 20 +- 5 files changed, 1634 insertions(+), 1458 deletions(-) create mode 100644 framework/src/com/phonegap/ExifHelper.java diff --git a/framework/src/com/phonegap/CameraLauncher.java b/framework/src/com/phonegap/CameraLauncher.java index 6ca85c43..58526653 100755 --- a/framework/src/com/phonegap/CameraLauncher.java +++ b/framework/src/com/phonegap/CameraLauncher.java @@ -35,100 +35,102 @@ import android.util.Log; */ public class CameraLauncher extends Plugin { - 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) - - private static final int JPEG = 0; // Take a picture of type JPEG - private static final int PNG = 1; // Take a picture of type PNG - - private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - private int targetWidth; // desired width of the image - private int targetHeight; // desired height of the image - private Uri imageUri; // Uri of captured image - public String callbackId; - + 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) + + private static final int JPEG = 0; // Take a picture of type JPEG + private static final int PNG = 1; // Take a picture of type PNG + + private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + private int targetWidth; // desired width of the image + private int targetHeight; // desired height of the image + private Uri imageUri; + private int encodingType; + // Uri of captured image + public String callbackId; + /** * Constructor. */ - public CameraLauncher() { - } + public CameraLauncher() { + } - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackId The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. - */ - public PluginResult execute(String action, JSONArray args, String callbackId) { - PluginResult.Status status = PluginResult.Status.OK; - String result = ""; - this.callbackId = callbackId; - - try { - if (action.equals("takePicture")) { - int destType = DATA_URL; - if (args.length() > 1) { - destType = args.getInt(1); - } - int srcType = CAMERA; - if (args.length() > 2) { - srcType = args.getInt(2); - } + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackId The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + this.callbackId = callbackId; + + try { + if (action.equals("takePicture")) { + int destType = DATA_URL; + if (args.length() > 1) { + destType = args.getInt(1); + } + int srcType = CAMERA; + if (args.length() > 2) { + srcType = args.getInt(2); + } if (args.length() > 3) { this.targetWidth = args.getInt(3); } if (args.length() > 4) { this.targetHeight = args.getInt(4); } - int encodingType = JPEG; + this.encodingType = JPEG; if (args.length() > 5) { - encodingType = args.getInt(5); + this.encodingType = args.getInt(5); } - if (srcType == CAMERA) { - this.takePicture(args.getInt(0), destType, encodingType); - } - else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { - this.getImage(args.getInt(0), srcType, destType); - } - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - return r; - } - return new PluginResult(status, result); - } catch (JSONException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } - } - + if (srcType == CAMERA) { + this.takePicture(args.getInt(0), destType, encodingType); + } + else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { + this.getImage(args.getInt(0), srcType, destType); + } + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + } + return new PluginResult(status, result); + } catch (JSONException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- - /** - * Take a picture with the camera. - * When an image is captured or the camera view is cancelled, the result is returned - * in PhonegapActivity.onActivityResult, which forwards the result to this.onActivityResult. - * - * The image can either be returned as a base64 string or a URI that points to the file. - * To display base64 string in an img tag, set the source to: - * img.src="data:image/jpeg;base64,"+result; - * or to display URI in an img tag - * img.src=result; - * - * @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, int returnType, int encodingType) { - this.mQuality = quality; - - // Display camera + /** + * Take a picture with the camera. + * When an image is captured or the camera view is cancelled, the result is returned + * in PhonegapActivity.onActivityResult, which forwards the result to this.onActivityResult. + * + * The image can either be returned as a base64 string or a URI that points to the file. + * To display base64 string in an img tag, set the source to: + * img.src="data:image/jpeg;base64,"+result; + * or to display URI in an img tag + * img.src=result; + * + * @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, int returnType, int encodingType) { + this.mQuality = quality; + + // Display camera Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); // Specify file so that large image is captured and returned @@ -138,14 +140,14 @@ public class CameraLauncher extends Plugin { this.imageUri = Uri.fromFile(photo); this.ctx.startActivityForResult((Plugin) this, intent, (CAMERA+1)*16 + returnType+1); - } + } - /** - * Create a file in the applications temporary directory based upon the supplied encoding. - * - * @param encodingType of the image to be taken - * @return a File object pointing to the temporary picture - */ + /** + * Create a file in the applications temporary directory based upon the supplied encoding. + * + * @param encodingType of the image to be taken + * @return a File object pointing to the temporary picture + */ private File createCaptureFile(int encodingType) { File photo = null; if (encodingType == JPEG) { @@ -157,46 +159,46 @@ public class CameraLauncher extends Plugin { } /** - * Get image from photo library. - * - * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - * @param srcType The album to get image from. - * @param returnType Set the type of image to return. - */ - // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! - public void getImage(int quality, int srcType, int returnType) { - this.mQuality = quality; + * Get image from photo library. + * + * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + * @param srcType The album to get image from. + * @param returnType Set the type of image to return. + */ + // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! + public void getImage(int quality, int srcType, int returnType) { + this.mQuality = quality; - 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); - } + 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); + } - /** - * Scales the bitmap according to the requested size. - * - * @param bitmap The bitmap to scale. - * @return Bitmap A new Bitmap object of the same bitmap after scaling. - */ - public Bitmap scaleBitmap(Bitmap bitmap) { + /** + * Scales the bitmap according to the requested size. + * + * @param bitmap The bitmap to scale. + * @return Bitmap A new Bitmap object of the same bitmap after scaling. + */ + public Bitmap scaleBitmap(Bitmap bitmap) { int newWidth = this.targetWidth; int newHeight = this.targetHeight; int origWidth = bitmap.getWidth(); int origHeight = bitmap.getHeight(); // If no new width or height were specified return the original bitmap - if (newWidth <= 0 && newHeight <= 0) { - return bitmap; - } - // Only the width was specified - else if (newWidth > 0 && newHeight <= 0) { + if (newWidth <= 0 && newHeight <= 0) { + return bitmap; + } + // Only the width was specified + else if (newWidth > 0 && newHeight <= 0) { newHeight = (newWidth * origHeight) / origWidth; } - // only the height was specified - else if (newWidth <= 0 && newHeight > 0) { + // only the height was specified + else if (newWidth <= 0 && newHeight > 0) { newWidth = (newHeight * origWidth) / origHeight; } // If the user specified both a positive width and height @@ -205,7 +207,7 @@ public class CameraLauncher extends Plugin { // Alternatively, the specified width and height could have been // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this // would result in whitespace in the new image. - else { + else { double newRatio = newWidth / (double)newHeight; double origRatio = origWidth / (double)origHeight; @@ -217,156 +219,169 @@ public class CameraLauncher extends Plugin { } return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); - } - + } + /** * Called when the camera view exits. * - * @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 intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @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 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; + 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 CAMERA - if (srcType == CAMERA) { - // If image available - if (resultCode == Activity.RESULT_OK) { - try { - // Read in bitmap of captured image - Bitmap bitmap; - try { - bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); - } catch (FileNotFoundException e) { - Uri uri = intent.getData(); - android.content.ContentResolver resolver = this.ctx.getContentResolver(); - bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); - } + // If CAMERA + if (srcType == CAMERA) { + // If image available + if (resultCode == Activity.RESULT_OK) { + try { + // Create an ExifHelper to save the exif data that is lost during compression + ExifHelper exif = new ExifHelper(); + if (this.encodingType == JPEG) { + exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx) + "/Pic.jpg"); + exif.readExifData(); + } - bitmap = scaleBitmap(bitmap); - - // If sending base64 image back - if (destType == DATA_URL) { - this.processPicture(bitmap); - } + // Read in bitmap of captured image + Bitmap bitmap; + try { + bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + } catch (FileNotFoundException e) { + Uri uri = intent.getData(); + android.content.ContentResolver resolver = this.ctx.getContentResolver(); + bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); + } - // 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; - } - } + bitmap = scaleBitmap(bitmap); + + // If sending base64 image back + if (destType == DATA_URL) { + this.processPicture(bitmap); + } - // 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(); + // 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; + } + } - // Send Uri back to JavaScript for viewing image - this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); - } - bitmap.recycle(); - bitmap = null; - System.gc(); - } catch (IOException e) { - e.printStackTrace(); - this.failPicture("Error capturing image."); - } - } + // 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(); + + // Restore exif data to file + if (this.encodingType == JPEG) { + exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx)); + exif.writeExifData(); + } - // If cancelled - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Camera cancelled."); - } + // Send Uri back to JavaScript for viewing image + this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); + } + bitmap.recycle(); + bitmap = null; + System.gc(); + } catch (IOException e) { + e.printStackTrace(); + this.failPicture("Error capturing image."); + } + } - // 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 (destType == DATA_URL) { - try { - Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); - bitmap = scaleBitmap(bitmap); - this.processPicture(bitmap); - bitmap.recycle(); - bitmap = null; - System.gc(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - this.failPicture("Error retrieving image."); - } - } - - // If sending filename back - else if (destType == FILE_URI) { - this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); - } - } - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Selection cancelled."); - } - else { - this.failPicture("Selection did not complete!"); - } - } - } - - /** - * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript. - * - * @param bitmap - */ - public void processPicture(Bitmap bitmap) { - ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); - try { - if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) { - byte[] code = jpeg_data.toByteArray(); - byte[] output = Base64.encodeBase64(code); - String js_out = new String(output); - this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId); - js_out = null; - output = null; - code = null; - } - } - catch(Exception e) { - this.failPicture("Error compressing image."); - } - jpeg_data = null; - } - - /** - * Send error message to JavaScript. - * - * @param err - */ - public void failPicture(String err) { - this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); - } -} + // 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 (destType == DATA_URL) { + try { + Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); + bitmap = scaleBitmap(bitmap); + this.processPicture(bitmap); + bitmap.recycle(); + bitmap = null; + System.gc(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + this.failPicture("Error retrieving image."); + } + } + + // If sending filename back + else if (destType == FILE_URI) { + this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); + } + } + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Selection cancelled."); + } + else { + this.failPicture("Selection did not complete!"); + } + } + } + + /** + * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript. + * + * @param bitmap + */ + public void processPicture(Bitmap bitmap) { + ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); + try { + if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) { + byte[] code = jpeg_data.toByteArray(); + byte[] output = Base64.encodeBase64(code); + String js_out = new String(output); + this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId); + js_out = null; + output = null; + code = null; + } + } + catch(Exception e) { + this.failPicture("Error compressing image."); + } + jpeg_data = null; + } + + /** + * Send error message to JavaScript. + * + * @param err + */ + public void failPicture(String err) { + this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/Capture.java b/framework/src/com/phonegap/Capture.java index b078e5df..e9261442 100644 --- a/framework/src/com/phonegap/Capture.java +++ b/framework/src/com/phonegap/Capture.java @@ -18,12 +18,10 @@ import org.json.JSONObject; import android.app.Activity; import android.content.ContentValues; import android.content.Intent; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaPlayer; import android.net.Uri; -import android.os.Environment; import android.util.Log; import com.phonegap.api.Plugin; @@ -31,146 +29,145 @@ import com.phonegap.api.PluginResult; public class Capture extends Plugin { - private static final String _DATA = "_data"; // The column name where the file path is stored - private static final int CAPTURE_AUDIO = 0; // Constant for capture audio - private static final int CAPTURE_IMAGE = 1; // Constant for capture image - private static final int CAPTURE_VIDEO = 2; // Constant for capture video - private static final String LOG_TAG = "Capture"; - private String callbackId; // The ID of the callback to be invoked with our result - private long limit; // the number of pics/vids/clips to take - private double duration; // optional duration parameter for video recording - private JSONArray results; // The array of results to be returned to the user - private Uri imageUri; // Uri of captured image + private static final int CAPTURE_AUDIO = 0; // Constant for capture audio + private static final int CAPTURE_IMAGE = 1; // Constant for capture image + private static final int CAPTURE_VIDEO = 2; // Constant for capture video + private static final String LOG_TAG = "Capture"; + private String callbackId; // The ID of the callback to be invoked with our result + private long limit; // the number of pics/vids/clips to take + private double duration; // optional duration parameter for video recording + private JSONArray results; // The array of results to be returned to the user + private Uri imageUri; // Uri of captured image - @Override - public PluginResult execute(String action, JSONArray args, String callbackId) { - this.callbackId = callbackId; - this.limit = 1; - this.duration = 0.0f; - this.results = new JSONArray(); - - JSONObject options = args.optJSONObject(0); - if (options != null) { - limit = options.optLong("limit", 1); - duration = options.optDouble("duration", 0.0f); - } + @Override + public PluginResult execute(String action, JSONArray args, String callbackId) { + this.callbackId = callbackId; + this.limit = 1; + this.duration = 0.0f; + this.results = new JSONArray(); + + JSONObject options = args.optJSONObject(0); + if (options != null) { + limit = options.optLong("limit", 1); + duration = options.optDouble("duration", 0.0f); + } - if (action.equals("getFormatData")) { - try { - JSONObject obj = getFormatData(args.getString(0), args.getString(1)); - return new PluginResult(PluginResult.Status.OK, obj); - } catch (JSONException e) { - return new PluginResult(PluginResult.Status.ERROR); - } - } - else if (action.equals("captureAudio")) { - this.captureAudio(); - } - else if (action.equals("captureImage")) { - this.captureImage(); - } - else if (action.equals("captureVideo")) { - this.captureVideo(duration); - } - - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - return r; - } + if (action.equals("getFormatData")) { + try { + JSONObject obj = getFormatData(args.getString(0), args.getString(1)); + return new PluginResult(PluginResult.Status.OK, obj); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.ERROR); + } + } + else if (action.equals("captureAudio")) { + this.captureAudio(); + } + else if (action.equals("captureImage")) { + this.captureImage(); + } + else if (action.equals("captureVideo")) { + this.captureVideo(duration); + } + + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + } - /** - * Provides the media data file data depending on it's mime type - * - * @param filePath path to the file - * @param mimeType of the file - * @return a MediaFileData object - */ - private JSONObject getFormatData(String filePath, String mimeType) { - JSONObject obj = new JSONObject(); - try { - // setup defaults - obj.put("height", 0); - obj.put("width", 0); - obj.put("bitrate", 0); - obj.put("duration", 0); - obj.put("codecs", ""); + /** + * Provides the media data file data depending on it's mime type + * + * @param filePath path to the file + * @param mimeType of the file + * @return a MediaFileData object + */ + private JSONObject getFormatData(String filePath, String mimeType) { + JSONObject obj = new JSONObject(); + try { + // setup defaults + obj.put("height", 0); + obj.put("width", 0); + obj.put("bitrate", 0); + obj.put("duration", 0); + obj.put("codecs", ""); - // If the mimeType isn't set the rest will fail - // so let's see if we can determine it. - if (mimeType == null || mimeType.equals("")) { - mimeType = FileUtils.getMimeType(filePath); - } - - if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) { - obj = getImageData(filePath, obj); - } - else if (filePath.endsWith("audio/3gpp")) { - obj = getAudioVideoData(filePath, obj, false); - } - else if (mimeType.equals("video/3gpp")) { - obj = getAudioVideoData(filePath, obj, true); - } - } - catch (JSONException e) { - Log.d(LOG_TAG, "Error: setting media file data object"); - } - return obj; - } + // If the mimeType isn't set the rest will fail + // so let's see if we can determine it. + if (mimeType == null || mimeType.equals("")) { + mimeType = FileUtils.getMimeType(filePath); + } + + if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) { + obj = getImageData(filePath, obj); + } + else if (filePath.endsWith("audio/3gpp")) { + obj = getAudioVideoData(filePath, obj, false); + } + else if (mimeType.equals("video/3gpp")) { + obj = getAudioVideoData(filePath, obj, true); + } + } + catch (JSONException e) { + Log.d(LOG_TAG, "Error: setting media file data object"); + } + return obj; + } - /** - * Get the Image specific attributes - * - * @param filePath path to the file - * @param obj represents the Media File Data - * @return a JSONObject that represents the Media File Data - * @throws JSONException - */ - private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException { - Bitmap bitmap = BitmapFactory.decodeFile(filePath); - obj.put("height", bitmap.getHeight()); - obj.put("width", bitmap.getWidth()); - return obj; - } + /** + * Get the Image specific attributes + * + * @param filePath path to the file + * @param obj represents the Media File Data + * @return a JSONObject that represents the Media File Data + * @throws JSONException + */ + private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException { + Bitmap bitmap = BitmapFactory.decodeFile(filePath); + obj.put("height", bitmap.getHeight()); + obj.put("width", bitmap.getWidth()); + return obj; + } - /** - * Get the Image specific attributes - * - * @param filePath path to the file - * @param obj represents the Media File Data - * @param video if true get video attributes as well - * @return a JSONObject that represents the Media File Data - * @throws JSONException - */ - private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException { - MediaPlayer player = new MediaPlayer(); - try { - player.setDataSource(filePath); - player.prepare(); - obj.put("duration", player.getDuration()); - if (video) { - obj.put("height", player.getVideoHeight()); - obj.put("width", player.getVideoWidth()); - } - } - catch (IOException e) { - Log.d(LOG_TAG, "Error: loading video file"); - } - return obj; - } + /** + * Get the Image specific attributes + * + * @param filePath path to the file + * @param obj represents the Media File Data + * @param video if true get video attributes as well + * @return a JSONObject that represents the Media File Data + * @throws JSONException + */ + private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException { + MediaPlayer player = new MediaPlayer(); + try { + player.setDataSource(filePath); + player.prepare(); + obj.put("duration", player.getDuration()); + if (video) { + obj.put("height", player.getVideoHeight()); + obj.put("width", player.getVideoWidth()); + } + } + catch (IOException e) { + Log.d(LOG_TAG, "Error: loading video file"); + } + return obj; + } - /** - * Sets up an intent to capture audio. Result handled by onActivityResult() - */ - private void captureAudio() { + /** + * Sets up an intent to capture audio. Result handled by onActivityResult() + */ + private void captureAudio() { Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_AUDIO); } - /** - * Sets up an intent to capture images. Result handled by onActivityResult() - */ - private void captureImage() { + /** + * Sets up an intent to capture images. Result handled by onActivityResult() + */ + private void captureImage() { Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); // Specify file so that large image is captured and returned @@ -181,179 +178,174 @@ public class Capture extends Plugin { this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_IMAGE); } - /** - * Sets up an intent to capture video. Result handled by onActivityResult() - */ - private void captureVideo(double duration) { + /** + * Sets up an intent to capture video. Result handled by onActivityResult() + */ + private void captureVideo(double duration) { Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); // Introduced in API 8 //intent.putExtra(android.provider.MediaStore.EXTRA_DURATION_LIMIT, duration); this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_VIDEO); } - + /** * Called when the video view exits. * - * @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 intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @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 intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). * @throws JSONException */ - public void onActivityResult(int requestCode, int resultCode, Intent intent) { + public void onActivityResult(int requestCode, int resultCode, Intent intent) { - // Result received okay - if (resultCode == Activity.RESULT_OK) { - // An audio clip was requested - if (requestCode == CAPTURE_AUDIO) { - // Get the uri of the audio clip - Uri data = intent.getData(); - // create a file object from the uri - results.put(createMediaFile(data)); + // Result received okay + if (resultCode == Activity.RESULT_OK) { + // An audio clip was requested + if (requestCode == CAPTURE_AUDIO) { + // Get the uri of the audio clip + Uri data = intent.getData(); + // create a file object from the uri + results.put(createMediaFile(data)); - if (results.length() >= limit) { - // Send Uri back to JavaScript for listening to audio - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more audio clips - captureAudio(); - } - } else if (requestCode == CAPTURE_IMAGE) { - // For some reason if I try to do: - // Uri data = intent.getData(); - // It crashes in the emulator and on my phone with a null pointer exception - // To work around it I had to grab the code from CameraLauncher.java - try { - // Read in bitmap of captured image - Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + if (results.length() >= limit) { + // Send Uri back to JavaScript for listening to audio + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more audio clips + captureAudio(); + } + } else if (requestCode == CAPTURE_IMAGE) { + // For some reason if I try to do: + // Uri data = intent.getData(); + // It crashes in the emulator and on my phone with a null pointer exception + // To work around it I had to grab the code from CameraLauncher.java + try { + // Create an ExifHelper to save the exif data that is lost during compression + ExifHelper exif = new ExifHelper(); + exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx) + "/Capture.jpg"); + exif.readExifData(); + + // Read in bitmap of captured image + Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); - // 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.fail("Error capturing image - no media storage found."); - return; - } - } + // 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.fail("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, 100, os); - os.close(); + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.close(); - bitmap.recycle(); - bitmap = null; - System.gc(); - - // Add image to results - results.put(createMediaFile(uri)); - - if (results.length() >= limit) { - // Send Uri back to JavaScript for viewing image - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more images - captureImage(); - } - } catch (IOException e) { - e.printStackTrace(); - this.fail("Error capturing image."); - } - } else if (requestCode == CAPTURE_VIDEO) { - // Get the uri of the video clip - Uri data = intent.getData(); - // create a file object from the uri - results.put(createMediaFile(data)); + bitmap.recycle(); + bitmap = null; + System.gc(); + + // Restore exif data to file + exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx)); + exif.writeExifData(); + + // Add image to results + results.put(createMediaFile(uri)); + + if (results.length() >= limit) { + // Send Uri back to JavaScript for viewing image + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more images + captureImage(); + } + } catch (IOException e) { + e.printStackTrace(); + this.fail("Error capturing image."); + } + } else if (requestCode == CAPTURE_VIDEO) { + // Get the uri of the video clip + Uri data = intent.getData(); + // create a file object from the uri + results.put(createMediaFile(data)); - if (results.length() >= limit) { - // Send Uri back to JavaScript for viewing video - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more video clips - captureVideo(duration); - } - } - } - // If canceled - else if (resultCode == Activity.RESULT_CANCELED) { - // If we have partial results send them back to the user - if (results.length() > 0) { - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } - // user canceled the action - else { - this.fail("Canceled."); - } - } - // If something else - else { - // If we have partial results send them back to the user - if (results.length() > 0) { - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } - // something bad happened - else { - this.fail("Did not complete!"); - } - } - } - - /** - * Creates a JSONObject that represents a File from the Uri - * - * @param data the Uri of the audio/image/video - * @return a JSONObject that represents a File - */ - private JSONObject createMediaFile(Uri data) { - File fp = new File(getRealPathFromURI(data)); - - JSONObject obj = new JSONObject(); - - try { - // File properties - obj.put("name", fp.getName()); - obj.put("fullPath", fp.getAbsolutePath()); - obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); - obj.put("lastModifiedDate", fp.lastModified()); - obj.put("size", fp.length()); - } catch (JSONException e) { - // this will never happen - e.printStackTrace(); - } - - return obj; - } - - /** - * Queries the media store to find out what the file path is for the Uri we supply - * - * @param contentUri the Uri of the audio/image/video - * @return the full path to the file - */ - private String getRealPathFromURI(Uri contentUri) { - String[] proj = { _DATA }; - Cursor cursor = this.ctx.managedQuery(contentUri, proj, null, null, null); - int column_index = cursor.getColumnIndexOrThrow(_DATA); - cursor.moveToFirst(); - return cursor.getString(column_index); + if (results.length() >= limit) { + // Send Uri back to JavaScript for viewing video + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more video clips + captureVideo(duration); + } + } + } + // If canceled + else if (resultCode == Activity.RESULT_CANCELED) { + // If we have partial results send them back to the user + if (results.length() > 0) { + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } + // user canceled the action + else { + this.fail("Canceled."); + } + } + // If something else + else { + // If we have partial results send them back to the user + if (results.length() > 0) { + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } + // something bad happened + else { + this.fail("Did not complete!"); + } + } } - /** - * Send error message to JavaScript. - * - * @param err - */ - public void fail(String err) { - this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); - } + /** + * Creates a JSONObject that represents a File from the Uri + * + * @param data the Uri of the audio/image/video + * @return a JSONObject that represents a File + * @throws IOException + */ + private JSONObject createMediaFile(Uri data){ + File fp = new File(FileUtils.getRealPathFromURI(data, this.ctx)); + JSONObject obj = new JSONObject(); + + try { + // File properties + obj.put("name", fp.getName()); + obj.put("fullPath", fp.getAbsolutePath()); + obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); + obj.put("lastModifiedDate", fp.lastModified()); + obj.put("size", fp.length()); + } catch (JSONException e) { + // this will never happen + e.printStackTrace(); + } + + return obj; + } + + /** + * Send error message to JavaScript. + * + * @param err + */ + public void fail(String err) { + this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); + } } \ No newline at end of file diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 559be52e..fc210341 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -91,90 +91,90 @@ import com.phonegap.api.PluginManager; * * Properties: The application can be configured using the following properties: * - * // Display a native loading dialog. Format for value = "Title,Message". - * // (String - default=null) - * super.setStringProperty("loadingDialog", "Wait,Loading Demo..."); + * // Display a native loading dialog. Format for value = "Title,Message". + * // (String - default=null) + * super.setStringProperty("loadingDialog", "Wait,Loading Demo..."); * - * // Display a native loading dialog when loading sub-pages. Format for value = "Title,Message". - * // (String - default=null) - * super.setStringProperty("loadingPageDialog", "Loading page..."); + * // Display a native loading dialog when loading sub-pages. Format for value = "Title,Message". + * // (String - default=null) + * super.setStringProperty("loadingPageDialog", "Loading page..."); * - * // Cause all links on web page to be loaded into existing web view, - * // instead of being loaded into new browser. (Boolean - default=false) - * super.setBooleanProperty("loadInWebView", true); + * // Cause all links on web page to be loaded into existing web view, + * // instead of being loaded into new browser. (Boolean - default=false) + * super.setBooleanProperty("loadInWebView", true); * - * // Load a splash screen image from the resource drawable directory. - * // (Integer - default=0) - * super.setIntegerProperty("splashscreen", R.drawable.splash); + * // Load a splash screen image from the resource drawable directory. + * // (Integer - default=0) + * super.setIntegerProperty("splashscreen", R.drawable.splash); * - * // Set the background color. - * // (Integer - default=0 or BLACK) - * super.setIntegerProperty("backgroundColor", Color.WHITE); + * // Set the background color. + * // (Integer - default=0 or BLACK) + * super.setIntegerProperty("backgroundColor", Color.WHITE); * - * // Time in msec to wait before triggering a timeout error when loading - * // with super.loadUrl(). (Integer - default=20000) - * super.setIntegerProperty("loadUrlTimeoutValue", 60000); + * // Time in msec to wait before triggering a timeout error when loading + * // with super.loadUrl(). (Integer - default=20000) + * super.setIntegerProperty("loadUrlTimeoutValue", 60000); * - * // URL to load if there's an error loading specified URL with loadUrl(). - * // Should be a local URL starting with file://. (String - default=null) - * super.setStringProperty("errorUrl", "file:///android_asset/www/error.html"); + * // URL to load if there's an error loading specified URL with loadUrl(). + * // Should be a local URL starting with file://. (String - default=null) + * super.setStringProperty("errorUrl", "file:///android_asset/www/error.html"); * - * // Enable app to keep running in background. (Boolean - default=true) - * super.setBooleanProperty("keepRunning", false); + * // Enable app to keep running in background. (Boolean - default=true) + * super.setBooleanProperty("keepRunning", false); */ public class DroidGap extends PhonegapActivity { - // The webview for our app - protected WebView appView; - protected WebViewClient webViewClient; + // The webview for our app + protected WebView appView; + protected WebViewClient webViewClient; - protected LinearLayout root; - public boolean bound = false; - public CallbackServer callbackServer; - protected PluginManager pluginManager; - protected boolean cancelLoadUrl = false; - protected boolean clearHistory = false; - protected ProgressDialog spinnerDialog = null; + protected LinearLayout root; + public boolean bound = false; + public CallbackServer callbackServer; + protected PluginManager pluginManager; + protected boolean cancelLoadUrl = false; + protected boolean clearHistory = false; + protected ProgressDialog spinnerDialog = null; - // The initial URL for our app - // ie http://server/path/index.html#abc?query - private String url; - - // The base of the initial URL for our app. - // Does not include file name. Ends with / - // ie http://server/path/ - private String baseUrl = null; + // The initial URL for our app + // ie http://server/path/index.html#abc?query + private String url; + + // The base of the initial URL for our app. + // Does not include file name. Ends with / + // ie http://server/path/ + private String baseUrl = null; - // Plugin to call when activity result is received - protected IPlugin activityResultCallback = null; - protected boolean activityResultKeepRunning; + // Plugin to call when activity result is received + protected IPlugin activityResultCallback = null; + protected boolean activityResultKeepRunning; - // Flag indicates that a loadUrl timeout occurred - private int loadUrlTimeout = 0; - - // Default background color for activity - // (this is not the color for the webview, which is set in HTML) - private int backgroundColor = Color.BLACK; - - /* - * The variables below are used to cache some of the activity properties. - */ + // Flag indicates that a loadUrl timeout occurred + private int loadUrlTimeout = 0; + + // Default background color for activity + // (this is not the color for the webview, which is set in HTML) + private int backgroundColor = Color.BLACK; + + /* + * The variables below are used to cache some of the activity properties. + */ - // Flag indicates that a URL navigated to from PhoneGap app should be loaded into same webview - // instead of being loaded into the web browser. - protected boolean loadInWebView = false; + // Flag indicates that a URL navigated to from PhoneGap app should be loaded into same webview + // instead of being loaded into the web browser. + protected boolean loadInWebView = false; - // Draw a splash screen using an image located in the drawable resource directory. - // This is not the same as calling super.loadSplashscreen(url) - protected int splashscreen = 0; + // Draw a splash screen using an image located in the drawable resource directory. + // This is not the same as calling super.loadSplashscreen(url) + protected int splashscreen = 0; - // LoadUrl timeout value in msec (default of 20 sec) - protected int loadUrlTimeoutValue = 20000; - - // Keep app running when pause is received. (default = true) - // If true, then the JavaScript and native code continue to run in the background - // when another application (activity) is started. - protected boolean keepRunning = true; + // LoadUrl timeout value in msec (default of 20 sec) + protected int loadUrlTimeoutValue = 20000; + + // Keep app running when pause is received. (default = true) + // If true, then the JavaScript and native code continue to run in the background + // when another application (activity) is started. + protected boolean keepRunning = true; /** * Called when the activity is first created. @@ -183,55 +183,55 @@ public class DroidGap extends PhonegapActivity { */ @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - // This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket! + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + // This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket! - Display display = getWindowManager().getDefaultDisplay(); - int width = display.getWidth(); - int height = display.getHeight(); - - root = new LinearLayoutSoftKeyboardDetect(this, width, height); - root.setOrientation(LinearLayout.VERTICAL); - root.setBackgroundColor(this.backgroundColor); - root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT, 0.0F)); + Display display = getWindowManager().getDefaultDisplay(); + int width = display.getWidth(); + int height = display.getHeight(); + + root = new LinearLayoutSoftKeyboardDetect(this, width, height); + root.setOrientation(LinearLayout.VERTICAL); + root.setBackgroundColor(this.backgroundColor); + root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT, 0.0F)); - // If url was passed in to intent, then init webview, which will load the url - Bundle bundle = this.getIntent().getExtras(); - if (bundle != null) { - String url = bundle.getString("url"); - if (url != null) { - this.init(); - } - } - // Setup the hardware volume controls to handle volume control - setVolumeControlStream(AudioManager.STREAM_MUSIC); + // If url was passed in to intent, then init webview, which will load the url + Bundle bundle = this.getIntent().getExtras(); + if (bundle != null) { + String url = bundle.getString("url"); + if (url != null) { + this.init(); + } + } + // Setup the hardware volume controls to handle volume control + setVolumeControlStream(AudioManager.STREAM_MUSIC); } /** * Create and initialize web container. */ - public void init() { - - // Create web container - this.appView = new WebView(DroidGap.this); - this.appView.setId(100); - - this.appView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT, - 1.0F)); + public void init() { + + // Create web container + this.appView = new WebView(DroidGap.this); + this.appView.setId(100); + + this.appView.setLayoutParams(new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT, + 1.0F)); WebViewReflect.checkCompatibility(); if (android.os.Build.VERSION.RELEASE.startsWith("1.")) { - this.appView.setWebChromeClient(new GapClient(DroidGap.this)); + this.appView.setWebChromeClient(new GapClient(DroidGap.this)); } else { - this.appView.setWebChromeClient(new EclairClient(DroidGap.this)); + this.appView.setWebChromeClient(new EclairClient(DroidGap.this)); } this.setWebViewClient(this.appView, new GapViewClient(this)); @@ -271,209 +271,209 @@ public class DroidGap extends PhonegapActivity { // If url specified, then load it String url = this.getStringProperty("url", null); if (url != null) { - System.out.println("Loading initial URL="+url); - this.loadUrl(url); + System.out.println("Loading initial URL="+url); + this.loadUrl(url); } - } - - /** - * Set the WebViewClient. - * - * @param appView - * @param client - */ - protected void setWebViewClient(WebView appView, WebViewClient client) { - this.webViewClient = client; - appView.setWebViewClient(client); - } + } + + /** + * Set the WebViewClient. + * + * @param appView + * @param client + */ + protected void setWebViewClient(WebView appView, WebViewClient client) { + this.webViewClient = client; + appView.setWebViewClient(client); + } /** * Bind PhoneGap objects to JavaScript. * * @param appView */ - private void bindBrowser(WebView appView) { - this.callbackServer = new CallbackServer(); - this.pluginManager = new PluginManager(appView, this); + private void bindBrowser(WebView appView) { + this.callbackServer = new CallbackServer(); + this.pluginManager = new PluginManager(appView, this); - } + } - /** - * Look at activity parameters and process them. - * This must be called from the main UI thread. - */ - private void handleActivityParameters() { + /** + * Look at activity parameters and process them. + * This must be called from the main UI thread. + */ + private void handleActivityParameters() { - // Init web view if not already done - if (this.appView == null) { - this.init(); - } + // Init web view if not already done + if (this.appView == null) { + this.init(); + } - // If backgroundColor - this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); - this.root.setBackgroundColor(this.backgroundColor); + // If backgroundColor + this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); + this.root.setBackgroundColor(this.backgroundColor); - // If spashscreen - this.splashscreen = this.getIntegerProperty("splashscreen", 0); - if (this.splashscreen != 0) { - root.setBackgroundResource(this.splashscreen); - } + // If spashscreen + this.splashscreen = this.getIntegerProperty("splashscreen", 0); + if (this.splashscreen != 0) { + root.setBackgroundResource(this.splashscreen); + } - // If loadInWebView - this.loadInWebView = this.getBooleanProperty("loadInWebView", false); + // If loadInWebView + this.loadInWebView = this.getBooleanProperty("loadInWebView", false); - // If loadUrlTimeoutValue - int timeout = this.getIntegerProperty("loadUrlTimeoutValue", 0); - if (timeout > 0) { - this.loadUrlTimeoutValue = timeout; - } - - // If keepRunning - this.keepRunning = this.getBooleanProperty("keepRunning", true); - } - + // If loadUrlTimeoutValue + int timeout = this.getIntegerProperty("loadUrlTimeoutValue", 0); + if (timeout > 0) { + this.loadUrlTimeoutValue = timeout; + } + + // If keepRunning + this.keepRunning = this.getBooleanProperty("keepRunning", true); + } + /** * Load the url into the webview. * * @param url */ - public void loadUrl(final String url) { - System.out.println("loadUrl("+url+")"); - this.url = url; - if (this.baseUrl == null) { - int i = url.lastIndexOf('/'); - if (i > 0) { - this.baseUrl = url.substring(0, i+1); - } - else { - this.baseUrl = this.url + "/"; - } - } - System.out.println("url="+url+" baseUrl="+baseUrl); + public void loadUrl(final String url) { + System.out.println("loadUrl("+url+")"); + this.url = url; + if (this.baseUrl == null) { + int i = url.lastIndexOf('/'); + if (i > 0) { + this.baseUrl = url.substring(0, i+1); + } + else { + this.baseUrl = this.url + "/"; + } + } + System.out.println("url="+url+" baseUrl="+baseUrl); - // Load URL on UI thread - final DroidGap me = this; - this.runOnUiThread(new Runnable() { - public void run() { + // Load URL on UI thread + final DroidGap me = this; + this.runOnUiThread(new Runnable() { + public void run() { - // Handle activity parameters - me.handleActivityParameters(); + // Handle activity parameters + me.handleActivityParameters(); - // Initialize callback server - me.callbackServer.init(url); + // Initialize callback server + me.callbackServer.init(url); - // If loadingDialog, then show the App loading dialog - String loading = me.getStringProperty("loadingDialog", null); - if (loading != null) { + // If loadingDialog, then show the App loading dialog + String loading = me.getStringProperty("loadingDialog", null); + if (loading != null) { - String title = ""; - String message = "Loading Application..."; + String title = ""; + String message = "Loading Application..."; - if (loading.length() > 0) { - int comma = loading.indexOf(','); - if (comma > 0) { - title = loading.substring(0, comma); - message = loading.substring(comma+1); - } - else { - title = ""; - message = loading; - } - } - me.spinnerStart(title, message); - } + if (loading.length() > 0) { + int comma = loading.indexOf(','); + if (comma > 0) { + title = loading.substring(0, comma); + message = loading.substring(comma+1); + } + else { + title = ""; + message = loading; + } + } + me.spinnerStart(title, message); + } - // Create a timeout timer for loadUrl - final int currentLoadUrlTimeout = me.loadUrlTimeout; - Runnable runnable = new Runnable() { - public void run() { - try { - synchronized(this) { - wait(me.loadUrlTimeoutValue); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } + // Create a timeout timer for loadUrl + final int currentLoadUrlTimeout = me.loadUrlTimeout; + Runnable runnable = new Runnable() { + public void run() { + try { + synchronized(this) { + wait(me.loadUrlTimeoutValue); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } - // If timeout, then stop loading and handle error - if (me.loadUrlTimeout == currentLoadUrlTimeout) { - me.appView.stopLoading(); - me.webViewClient.onReceivedError(me.appView, -6, "The connection to the server was unsuccessful.", url); - } - } - }; - Thread thread = new Thread(runnable); - thread.start(); - me.appView.loadUrl(url); - } - }); - } - - /** - * Load the url into the webview after waiting for period of time. - * This is used to display the splashscreen for certain amount of time. - * - * @param url - * @param time The number of ms to wait before loading webview - */ - public void loadUrl(final String url, final int time) { - System.out.println("loadUrl("+url+","+time+")"); - final DroidGap me = this; + // If timeout, then stop loading and handle error + if (me.loadUrlTimeout == currentLoadUrlTimeout) { + me.appView.stopLoading(); + me.webViewClient.onReceivedError(me.appView, -6, "The connection to the server was unsuccessful.", url); + } + } + }; + Thread thread = new Thread(runnable); + thread.start(); + me.appView.loadUrl(url); + } + }); + } + + /** + * Load the url into the webview after waiting for period of time. + * This is used to display the splashscreen for certain amount of time. + * + * @param url + * @param time The number of ms to wait before loading webview + */ + public void loadUrl(final String url, final int time) { + System.out.println("loadUrl("+url+","+time+")"); + final DroidGap me = this; - // Handle activity parameters - this.runOnUiThread(new Runnable() { - public void run() { - me.handleActivityParameters(); - } - }); + // Handle activity parameters + this.runOnUiThread(new Runnable() { + public void run() { + me.handleActivityParameters(); + } + }); - Runnable runnable = new Runnable() { - public void run() { - try { - synchronized(this) { - this.wait(time); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - if (!me.cancelLoadUrl) { - me.loadUrl(url); - } - else{ - me.cancelLoadUrl = false; - System.out.println("Aborting loadUrl("+url+"): Another URL was loaded before timer expired."); - } - } - }; - Thread thread = new Thread(runnable); - thread.start(); - } - - /** - * Cancel loadUrl before it has been loaded. - */ - public void cancelLoadUrl() { - this.cancelLoadUrl = true; - } - - /** - * Clear the resource cache. - */ - public void clearCache() { - if (this.appView == null) { - this.init(); - } - this.appView.clearCache(true); - } + Runnable runnable = new Runnable() { + public void run() { + try { + synchronized(this) { + this.wait(time); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (!me.cancelLoadUrl) { + me.loadUrl(url); + } + else{ + me.cancelLoadUrl = false; + System.out.println("Aborting loadUrl("+url+"): Another URL was loaded before timer expired."); + } + } + }; + Thread thread = new Thread(runnable); + thread.start(); + } + + /** + * Cancel loadUrl before it has been loaded. + */ + public void cancelLoadUrl() { + this.cancelLoadUrl = true; + } + + /** + * Clear the resource cache. + */ + public void clearCache() { + if (this.appView == null) { + this.init(); + } + this.appView.clearCache(true); + } /** * Clear web history in this web view. */ public void clearHistory() { - this.clearHistory = true; - if (this.appView != null) { - this.appView.clearHistory(); - } + this.clearHistory = true; + if (this.appView != null) { + this.appView.clearHistory(); + } } @Override @@ -483,8 +483,8 @@ public class DroidGap extends PhonegapActivity { * @param Configuration newConfig */ public void onConfigurationChanged(Configuration newConfig) { - //don't reload the current page when the orientation is changed - super.onConfigurationChanged(newConfig); + //don't reload the current page when the orientation is changed + super.onConfigurationChanged(newConfig); } /** @@ -495,15 +495,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public boolean getBooleanProperty(String name, boolean defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Boolean p = (Boolean)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.booleanValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Boolean p = (Boolean)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.booleanValue(); } /** @@ -514,15 +514,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public int getIntegerProperty(String name, int defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Integer p = (Integer)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.intValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Integer p = (Integer)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.intValue(); } /** @@ -533,15 +533,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public String getStringProperty(String name, String defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - String p = bundle.getString(name); - if (p == null) { - return defaultValue; - } - return p; + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + String p = bundle.getString(name); + if (p == null) { + return defaultValue; + } + return p; } /** @@ -552,15 +552,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public double getDoubleProperty(String name, double defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Double p = (Double)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.doubleValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Double p = (Double)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.doubleValue(); } /** @@ -570,7 +570,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setBooleanProperty(String name, boolean value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -580,7 +580,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setIntegerProperty(String name, int value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -590,7 +590,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setStringProperty(String name, String value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -600,7 +600,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setDoubleProperty(String name, double value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } @Override @@ -610,20 +610,20 @@ public class DroidGap extends PhonegapActivity { protected void onPause() { super.onPause(); if (this.appView == null) { - return; + return; } - // Send pause event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); + // Send pause event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); - // Forward to plugins - this.pluginManager.onPause(this.keepRunning); + // Forward to plugins + this.pluginManager.onPause(this.keepRunning); // If app doesn't want to run in background if (!this.keepRunning) { - // Pause JavaScript timers (including setInterval) - this.appView.pauseTimers(); + // Pause JavaScript timers (including setInterval) + this.appView.pauseTimers(); } } @@ -632,10 +632,10 @@ public class DroidGap extends PhonegapActivity { * Called when the activity receives a new intent **/ protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); + super.onNewIntent(intent); - //Forward to plugins - this.pluginManager.onNewIntent(intent); + //Forward to plugins + this.pluginManager.onNewIntent(intent); } @Override @@ -645,26 +645,26 @@ public class DroidGap extends PhonegapActivity { protected void onResume() { super.onResume(); if (this.appView == null) { - return; + return; } - // Send resume event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};"); + // Send resume event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};"); - // Forward to plugins - this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); + // Forward to plugins + this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); // If app doesn't want to run in background if (!this.keepRunning || this.activityResultKeepRunning) { - // Restore multitasking state - if (this.activityResultKeepRunning) { - this.keepRunning = this.activityResultKeepRunning; - this.activityResultKeepRunning = false; - } + // Restore multitasking state + if (this.activityResultKeepRunning) { + this.keepRunning = this.activityResultKeepRunning; + this.activityResultKeepRunning = false; + } - // Resume JavaScript timers (including setInterval) - this.appView.resumeTimers(); + // Resume JavaScript timers (including setInterval) + this.appView.resumeTimers(); } } @@ -673,21 +673,21 @@ public class DroidGap extends PhonegapActivity { * The final call you receive before your activity is destroyed. */ public void onDestroy() { - super.onDestroy(); - + super.onDestroy(); + if (this.appView != null) { - - // Make sure pause event is sent if onPause hasn't been called before onDestroy - this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); + + // Make sure pause event is sent if onPause hasn't been called before onDestroy + this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); - // Send destroy event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};"); + // Send destroy event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};"); - // Load blank page so that JavaScript onunload is called - this.appView.loadUrl("about:blank"); - - // Forward to plugins - this.pluginManager.onDestroy(); + // Load blank page so that JavaScript onunload is called + this.appView.loadUrl("about:blank"); + + // Forward to plugins + this.pluginManager.onDestroy(); } } @@ -699,7 +699,7 @@ public class DroidGap extends PhonegapActivity { * @param className */ public void addService(String serviceType, String className) { - this.pluginManager.addService(serviceType, className); + this.pluginManager.addService(serviceType, className); } /** @@ -709,7 +709,7 @@ public class DroidGap extends PhonegapActivity { * @param message */ public void sendJavascript(String statement) { - this.callbackServer.sendJavascript(statement); + this.callbackServer.sendJavascript(statement); } @@ -721,85 +721,85 @@ public class DroidGap extends PhonegapActivity { * * @param url The url to load. * @param usePhoneGap Load url in PhoneGap webview. - * @param clearPrev Clear the activity stack, so new app becomes top of stack - * @param params DroidGap parameters for new app + * @param clearPrev Clear the activity stack, so new app becomes top of stack + * @param params DroidGap parameters for new app * @throws android.content.ActivityNotFoundException */ public void showWebPage(String url, boolean usePhoneGap, boolean clearPrev, HashMap params) throws android.content.ActivityNotFoundException { - Intent intent = null; - if (usePhoneGap) { - try { - intent = new Intent().setClass(this, Class.forName(this.getComponentName().getClassName())); - intent.putExtra("url", url); + Intent intent = null; + if (usePhoneGap) { + try { + intent = new Intent().setClass(this, Class.forName(this.getComponentName().getClassName())); + intent.putExtra("url", url); - // Add parameters - if (params != null) { - java.util.Set> s = params.entrySet(); - java.util.Iterator> it = s.iterator(); - while(it.hasNext()) { - Entry entry = it.next(); - String key = entry.getKey(); - Object value = entry.getValue(); - if (value == null) { - } - else if (value.getClass().equals(String.class)) { - intent.putExtra(key, (String)value); - } - else if (value.getClass().equals(Boolean.class)) { - intent.putExtra(key, (Boolean)value); - } - else if (value.getClass().equals(Integer.class)) { - intent.putExtra(key, (Integer)value); - } - } + // Add parameters + if (params != null) { + java.util.Set> s = params.entrySet(); + java.util.Iterator> it = s.iterator(); + while(it.hasNext()) { + Entry entry = it.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + } + else if (value.getClass().equals(String.class)) { + intent.putExtra(key, (String)value); + } + else if (value.getClass().equals(Boolean.class)) { + intent.putExtra(key, (Boolean)value); + } + else if (value.getClass().equals(Integer.class)) { + intent.putExtra(key, (Integer)value); + } + } - } - } catch (ClassNotFoundException e) { - e.printStackTrace(); - intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - } - } - else { - intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - } - this.startActivity(intent); - - // Finish current activity - if (clearPrev) { - this.finish(); - } + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + } + } + else { + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + } + this.startActivity(intent); + + // Finish current activity + if (clearPrev) { + this.finish(); + } } /** * Show the spinner. Must be called from the UI thread. * - * @param title Title of the dialog - * @param message The message of the dialog + * @param title Title of the dialog + * @param message The message of the dialog */ public void spinnerStart(final String title, final String message) { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } - final DroidGap me = this; - this.spinnerDialog = ProgressDialog.show(DroidGap.this, title , message, true, true, - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - me.spinnerDialog = null; - } - }); + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; + } + final DroidGap me = this; + this.spinnerDialog = ProgressDialog.show(DroidGap.this, title , message, true, true, + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + me.spinnerDialog = null; + } + }); } /** * Stop spinner. */ public void spinnerStop() { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; + } } /** @@ -834,11 +834,11 @@ public class DroidGap extends PhonegapActivity { dlg.setTitle("Alert"); dlg.setCancelable(false); dlg.setPositiveButton(android.R.string.ok, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.confirm(); - } - }); + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.confirm(); + } + }); dlg.create(); dlg.show(); return true; @@ -859,15 +859,15 @@ public class DroidGap extends PhonegapActivity { dlg.setTitle("Confirm"); dlg.setCancelable(false); dlg.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.confirm(); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.confirm(); } }); dlg.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.cancel(); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.cancel(); } }); dlg.create(); @@ -888,91 +888,91 @@ public class DroidGap extends PhonegapActivity { */ @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { - - // Security check to make sure any requests are coming from the page initially - // loaded in webview and not another loaded in an iframe. - boolean reqOk = false; - if (url.indexOf(this.ctx.baseUrl) == 0) { - reqOk = true; - } - - // Calling PluginManager.exec() to call a native service using - // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); - if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { - JSONArray array; - try { - array = new JSONArray(defaultValue.substring(4)); - String service = array.getString(0); - String action = array.getString(1); - String callbackId = array.getString(2); - boolean async = array.getBoolean(3); - String r = pluginManager.exec(service, action, callbackId, message, async); - result.confirm(r); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - // Polling for JavaScript messages - else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { - String r = callbackServer.getJavascript(); - result.confirm(r); - } - - // Calling into CallbackServer - else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { - String r = ""; - if (message.equals("usePolling")) { - r = ""+callbackServer.usePolling(); - } - else if (message.equals("restartServer")) { - callbackServer.restartServer(); - } - else if (message.equals("getPort")) { - r = Integer.toString(callbackServer.getPort()); - } - else if (message.equals("getToken")) { - r = callbackServer.getToken(); - } - result.confirm(r); - } - - // PhoneGap JS has initialized, so show webview - // (This solves white flash seen when rendering HTML) - else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) { - appView.setVisibility(View.VISIBLE); - ctx.spinnerStop(); - result.confirm("OK"); - } + + // Security check to make sure any requests are coming from the page initially + // loaded in webview and not another loaded in an iframe. + boolean reqOk = false; + if (url.indexOf(this.ctx.baseUrl) == 0) { + reqOk = true; + } + + // Calling PluginManager.exec() to call a native service using + // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); + if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { + JSONArray array; + try { + array = new JSONArray(defaultValue.substring(4)); + String service = array.getString(0); + String action = array.getString(1); + String callbackId = array.getString(2); + boolean async = array.getBoolean(3); + String r = pluginManager.exec(service, action, callbackId, message, async); + result.confirm(r); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + // Polling for JavaScript messages + else if (reqOk && defaultValue.equals("gap_poll:")) { + String r = callbackServer.getJavascript(); + result.confirm(r); + } + + // Calling into CallbackServer + else if (reqOk && defaultValue.equals("gap_callbackServer:")) { + String r = ""; + if (message.equals("usePolling")) { + r = ""+callbackServer.usePolling(); + } + else if (message.equals("restartServer")) { + callbackServer.restartServer(); + } + else if (message.equals("getPort")) { + r = Integer.toString(callbackServer.getPort()); + } + else if (message.equals("getToken")) { + r = callbackServer.getToken(); + } + result.confirm(r); + } + + // PhoneGap JS has initialized, so show webview + // (This solves white flash seen when rendering HTML) + else if (reqOk && defaultValue.equals("gap_init:")) { + appView.setVisibility(View.VISIBLE); + ctx.spinnerStop(); + result.confirm("OK"); + } - // Show dialog - else { - final JsPromptResult res = result; - AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); - dlg.setMessage(message); - final EditText input = new EditText(this.ctx); - if (defaultValue != null) { - input.setText(defaultValue); - } - dlg.setView(input); - dlg.setCancelable(false); - dlg.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String usertext = input.getText().toString(); - res.confirm(usertext); - } - }); - dlg.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - res.cancel(); - } - }); - dlg.create(); - dlg.show(); - } - return true; + // Show dialog + else { + final JsPromptResult res = result; + AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); + dlg.setMessage(message); + final EditText input = new EditText(this.ctx); + if (defaultValue != null) { + input.setText(defaultValue); + } + dlg.setView(input); + dlg.setCancelable(false); + dlg.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String usertext = input.getText().toString(); + res.confirm(usertext); + } + }); + dlg.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + res.cancel(); + } + }); + dlg.create(); + dlg.show(); + } + return true; } } @@ -982,69 +982,69 @@ public class DroidGap extends PhonegapActivity { */ public class EclairClient extends GapClient { - private String TAG = "PhoneGapLog"; - private long MAX_QUOTA = 100 * 1024 * 1024; + private String TAG = "PhoneGapLog"; + private long MAX_QUOTA = 100 * 1024 * 1024; - /** - * Constructor. - * - * @param ctx - */ - public EclairClient(Context ctx) { - super(ctx); - } + /** + * Constructor. + * + * @param ctx + */ + public EclairClient(Context ctx) { + super(ctx); + } - /** - * Handle database quota exceeded notification. - * - * @param url - * @param databaseIdentifier - * @param currentQuota - * @param estimatedSize - * @param totalUsedQuota - * @param quotaUpdater - */ - @Override - public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, - long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) - { - Log.d(TAG, "event raised onExceededDatabaseQuota estimatedSize: " + Long.toString(estimatedSize) + " currentQuota: " + Long.toString(currentQuota) + " totalUsedQuota: " + Long.toString(totalUsedQuota)); + /** + * Handle database quota exceeded notification. + * + * @param url + * @param databaseIdentifier + * @param currentQuota + * @param estimatedSize + * @param totalUsedQuota + * @param quotaUpdater + */ + @Override + public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, + long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) + { + Log.d(TAG, "event raised onExceededDatabaseQuota estimatedSize: " + Long.toString(estimatedSize) + " currentQuota: " + Long.toString(currentQuota) + " totalUsedQuota: " + Long.toString(totalUsedQuota)); - if( estimatedSize < MAX_QUOTA) - { - //increase for 1Mb - long newQuota = estimatedSize; - Log.d(TAG, "calling quotaUpdater.updateQuota newQuota: " + Long.toString(newQuota) ); - quotaUpdater.updateQuota(newQuota); - } - else - { - // Set the quota to whatever it is and force an error - // TODO: get docs on how to handle this properly - quotaUpdater.updateQuota(currentQuota); - } - } + if( estimatedSize < MAX_QUOTA) + { + //increase for 1Mb + long newQuota = estimatedSize; + Log.d(TAG, "calling quotaUpdater.updateQuota newQuota: " + Long.toString(newQuota) ); + quotaUpdater.updateQuota(newQuota); + } + else + { + // Set the quota to whatever it is and force an error + // TODO: get docs on how to handle this properly + quotaUpdater.updateQuota(currentQuota); + } + } - // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html - @Override - public void onConsoleMessage(String message, int lineNumber, String sourceID) - { - // This is a kludgy hack!!!! - Log.d(TAG, sourceID + ": Line " + Integer.toString(lineNumber) + " : " + message); - } + // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html + @Override + public void onConsoleMessage(String message, int lineNumber, String sourceID) + { + // This is a kludgy hack!!!! + Log.d(TAG, sourceID + ": Line " + Integer.toString(lineNumber) + " : " + message); + } - @Override - /** - * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. - * - * @param origin - * @param callback - */ - public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { - // TODO Auto-generated method stub - super.onGeolocationPermissionsShowPrompt(origin, callback); - callback.invoke(origin, true, false); - } + @Override + /** + * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. + * + * @param origin + * @param callback + */ + public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { + // TODO Auto-generated method stub + super.onGeolocationPermissionsShowPrompt(origin, callback); + callback.invoke(origin, true, false); + } } @@ -1068,221 +1068,221 @@ public class DroidGap extends PhonegapActivity { * Give the host application a chance to take over the control when a new url * is about to be loaded in the current WebView. * - * @param view The WebView that is initiating the callback. - * @param url The url to be loaded. - * @return true to override, false for default behavior + * @param view The WebView that is initiating the callback. + * @param url The url to be loaded. + * @return true to override, false for default behavior */ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - // If dialing phone (tel:5551212) - if (url.startsWith(WebView.SCHEME_TEL)) { - try { - Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error dialing "+url+": "+ e.toString()); - } - return true; - } - - // If displaying map (geo:0,0?q=address) - else if (url.startsWith("geo:")) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error showing map "+url+": "+ e.toString()); - } - return true; - } - - // If sending email (mailto:abc@corp.com) - else if (url.startsWith(WebView.SCHEME_MAILTO)) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error sending email "+url+": "+ e.toString()); - } - return true; - } - - // If sms:5551212?body=This is the message - else if (url.startsWith("sms:")) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); + // If dialing phone (tel:5551212) + if (url.startsWith(WebView.SCHEME_TEL)) { + try { + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error dialing "+url+": "+ e.toString()); + } + return true; + } + + // If displaying map (geo:0,0?q=address) + else if (url.startsWith("geo:")) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error showing map "+url+": "+ e.toString()); + } + return true; + } + + // If sending email (mailto:abc@corp.com) + else if (url.startsWith(WebView.SCHEME_MAILTO)) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error sending email "+url+": "+ e.toString()); + } + return true; + } + + // If sms:5551212?body=This is the message + else if (url.startsWith("sms:")) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); - // Get address - String address = null; - int parmIndex = url.indexOf('?'); - if (parmIndex == -1) { - address = url.substring(4); - } - else { - address = url.substring(4, parmIndex); + // Get address + String address = null; + int parmIndex = url.indexOf('?'); + if (parmIndex == -1) { + address = url.substring(4); + } + else { + address = url.substring(4, parmIndex); - // If body, then set sms body - Uri uri = Uri.parse(url); - String query = uri.getQuery(); - if (query != null) { - if (query.startsWith("body=")) { - intent.putExtra("sms_body", query.substring(5)); - } - } - } - intent.setData(Uri.parse("sms:"+address)); - intent.putExtra("address", address); - intent.setType("vnd.android-dir/mms-sms"); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error sending sms "+url+":"+ e.toString()); - } - return true; - } + // If body, then set sms body + Uri uri = Uri.parse(url); + String query = uri.getQuery(); + if (query != null) { + if (query.startsWith("body=")) { + intent.putExtra("sms_body", query.substring(5)); + } + } + } + intent.setData(Uri.parse("sms:"+address)); + intent.putExtra("address", address); + intent.setType("vnd.android-dir/mms-sms"); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error sending sms "+url+":"+ e.toString()); + } + return true; + } - // All else - else { + // All else + else { - // If our app or file:, then load into our webview - // NOTE: This replaces our app with new URL. When BACK is pressed, - // our app is reloaded and restarted. All state is lost. - if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0) { - try { - // Init parameters to new DroidGap activity and propagate existing parameters - HashMap params = new HashMap(); - String loadingPage = this.ctx.getStringProperty("loadingPageDialog", null); - if (loadingPage != null) { - params.put("loadingDialog", loadingPage); - params.put("loadingPageDialog", loadingPage); - } - if (this.ctx.loadInWebView) { - params.put("loadInWebView", true); - } - params.put("keepRunning", this.ctx.keepRunning); - params.put("loadUrlTimeoutValue", this.ctx.loadUrlTimeoutValue); - String errorUrl = this.ctx.getStringProperty("errorUrl", null); - if (errorUrl != null) { - params.put("errorUrl", errorUrl); - } - params.put("backgroundColor", this.ctx.backgroundColor); + // If our app or file:, then load into our webview + // NOTE: This replaces our app with new URL. When BACK is pressed, + // our app is reloaded and restarted. All state is lost. + if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0) { + try { + // Init parameters to new DroidGap activity and propagate existing parameters + HashMap params = new HashMap(); + String loadingPage = this.ctx.getStringProperty("loadingPageDialog", null); + if (loadingPage != null) { + params.put("loadingDialog", loadingPage); + params.put("loadingPageDialog", loadingPage); + } + if (this.ctx.loadInWebView) { + params.put("loadInWebView", true); + } + params.put("keepRunning", this.ctx.keepRunning); + params.put("loadUrlTimeoutValue", this.ctx.loadUrlTimeoutValue); + String errorUrl = this.ctx.getStringProperty("errorUrl", null); + if (errorUrl != null) { + params.put("errorUrl", errorUrl); + } + params.put("backgroundColor", this.ctx.backgroundColor); - this.ctx.showWebPage(url, true, false, params); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error loading url into DroidGap - "+url+":"+ e.toString()); - } - } - - // If not our application, let default viewer handle - else { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error loading url "+url+":"+ e.toString()); - } - } - return true; - } + this.ctx.showWebPage(url, true, false, params); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error loading url into DroidGap - "+url+":"+ e.toString()); + } + } + + // If not our application, let default viewer handle + else { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error loading url "+url+":"+ e.toString()); + } + } + return true; + } } - + /** * Notify the host application that a page has finished loading. * - * @param view The webview initiating the callback. - * @param url The url of the page. + * @param view The webview initiating the callback. + * @param url The url of the page. */ @Override public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); + super.onPageFinished(view, url); - // Clear timeout flag - this.ctx.loadUrlTimeout++; + // Clear timeout flag + this.ctx.loadUrlTimeout++; - // Try firing the onNativeReady event in JS. If it fails because the JS is - // not loaded yet then just set a flag so that the onNativeReady can be fired - // from the JS side when the JS gets to that code. - if (!url.equals("about:blank")) { - appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); - } + // Try firing the onNativeReady event in JS. If it fails because the JS is + // not loaded yet then just set a flag so that the onNativeReady can be fired + // from the JS side when the JS gets to that code. + if (!url.equals("about:blank")) { + appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); + } - // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly - Thread t = new Thread(new Runnable() { - public void run() { - try { - Thread.sleep(2000); - ctx.runOnUiThread(new Runnable() { - public void run() { - appView.setVisibility(View.VISIBLE); - ctx.spinnerStop(); - } - }); - } catch (InterruptedException e) { - } - } - }); - t.start(); - + // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly + Thread t = new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(2000); + ctx.runOnUiThread(new Runnable() { + public void run() { + appView.setVisibility(View.VISIBLE); + ctx.spinnerStop(); + } + }); + } catch (InterruptedException e) { + } + } + }); + t.start(); + - // Clear history, so that previous screen isn't there when Back button is pressed - if (this.ctx.clearHistory) { - this.ctx.clearHistory = false; - this.ctx.appView.clearHistory(); - } - - // Shutdown if blank loaded - if (url.equals("about:blank")) { - if (this.ctx.callbackServer != null) { - this.ctx.callbackServer.destroy(); - } - } + // Clear history, so that previous screen isn't there when Back button is pressed + if (this.ctx.clearHistory) { + this.ctx.clearHistory = false; + this.ctx.appView.clearHistory(); + } + + // Shutdown if blank loaded + if (url.equals("about:blank")) { + if (this.ctx.callbackServer != null) { + this.ctx.callbackServer.destroy(); + } + } } /** * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable). * The errorCode parameter corresponds to one of the ERROR_* constants. * - * @param view The WebView that is initiating the callback. - * @param errorCode The error code corresponding to an ERROR_* value. - * @param description A String describing the error. - * @param failingUrl The url that failed to load. + * @param view The WebView that is initiating the callback. + * @param errorCode The error code corresponding to an ERROR_* value. + * @param description A String describing the error. + * @param failingUrl The url that failed to load. */ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - System.out.println("onReceivedError: Error code="+errorCode+" Description="+description+" URL="+failingUrl); + System.out.println("onReceivedError: Error code="+errorCode+" Description="+description+" URL="+failingUrl); - // Clear timeout flag - this.ctx.loadUrlTimeout++; + // Clear timeout flag + this.ctx.loadUrlTimeout++; - // Stop "app loading" spinner if showing - this.ctx.spinnerStop(); + // Stop "app loading" spinner if showing + this.ctx.spinnerStop(); - // Handle error - this.ctx.onReceivedError(errorCode, description, failingUrl); + // Handle error + this.ctx.onReceivedError(errorCode, description, failingUrl); } public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - final String packageName = this.ctx.getPackageName(); - final PackageManager pm = this.ctx.getPackageManager(); - ApplicationInfo appInfo; - try { - appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); - if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - // debug = true - handler.proceed(); - return; - } else { - // debug = false - super.onReceivedSslError(view, handler, error); - } - } catch (NameNotFoundException e) { - // When it doubt, lock it out! - super.onReceivedSslError(view, handler, error); - } + final String packageName = this.ctx.getPackageName(); + final PackageManager pm = this.ctx.getPackageManager(); + ApplicationInfo appInfo; + try { + appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + // debug = true + handler.proceed(); + return; + } else { + // debug = false + super.onReceivedSslError(view, handler, error); + } + } catch (NameNotFoundException e) { + // When it doubt, lock it out! + super.onReceivedSslError(view, handler, error); + } } } @@ -1295,47 +1295,47 @@ public class DroidGap extends PhonegapActivity { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (this.appView == null) { - return super.onKeyDown(keyCode, event); + return super.onKeyDown(keyCode, event); } - // If back key - if (keyCode == KeyEvent.KEYCODE_BACK) { + // If back key + if (keyCode == KeyEvent.KEYCODE_BACK) { - // If back key is bound, then send event to JavaScript - if (this.bound) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');"); - return true; - } + // If back key is bound, then send event to JavaScript + if (this.bound) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');"); + return true; + } - // If not bound - else { + // If not bound + else { - // Go to previous page in webview if it is possible to go back - if (this.appView.canGoBack()) { - this.appView.goBack(); - return true; - } + // Go to previous page in webview if it is possible to go back + if (this.appView.canGoBack()) { + this.appView.goBack(); + return true; + } - // If not, then invoke behavior of super class - else { - return super.onKeyDown(keyCode, event); - } - } - } + // If not, then invoke behavior of super class + else { + return super.onKeyDown(keyCode, event); + } + } + } - // If menu key - else if (keyCode == KeyEvent.KEYCODE_MENU) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('menubutton');"); - return true; - } + // If menu key + else if (keyCode == KeyEvent.KEYCODE_MENU) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('menubutton');"); + return true; + } - // If search key - else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('searchbutton');"); - return true; - } + // If search key + else if (keyCode == KeyEvent.KEYCODE_SEARCH) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('searchbutton');"); + return true; + } - return false; + return false; } /** @@ -1344,36 +1344,36 @@ public class DroidGap extends PhonegapActivity { * * This is done to eliminate the need to modify DroidGap.java to receive activity results. * - * @param intent The intent to start - * @param requestCode Identifies who to send the result to + * @param intent The intent to start + * @param requestCode Identifies who to send the result to * * @throws RuntimeException */ @Override public void startActivityForResult(Intent intent, int requestCode) throws RuntimeException { - System.out.println("startActivityForResult(intent,"+requestCode+")"); - super.startActivityForResult(intent, requestCode); + System.out.println("startActivityForResult(intent,"+requestCode+")"); + super.startActivityForResult(intent, requestCode); } /** * Launch an activity for which you would like a result when it finished. When this activity exits, * your onActivityResult() method will be called. * - * @param command The command object - * @param intent The intent to start - * @param requestCode The request code that is passed to callback to identify the activity + * @param command The command object + * @param intent The intent to start + * @param requestCode The request code that is passed to callback to identify the activity */ public void startActivityForResult(IPlugin command, Intent intent, int requestCode) { - this.activityResultCallback = command; - this.activityResultKeepRunning = this.keepRunning; - - // If multitasking turned on, then disable it for activities that return results - if (command != null) { - this.keepRunning = false; - } - - // Start activity - super.startActivityForResult(intent, requestCode); + this.activityResultCallback = command; + this.activityResultKeepRunning = this.keepRunning; + + // If multitasking turned on, then disable it for activities that return results + if (command != null) { + this.keepRunning = false; + } + + // Start activity + super.startActivityForResult(intent, requestCode); } @Override @@ -1381,52 +1381,52 @@ public class DroidGap extends PhonegapActivity { * 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"). + * @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"). */ protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - IPlugin callback = this.activityResultCallback; - if (callback != null) { - callback.onActivityResult(requestCode, resultCode, intent); - } + super.onActivityResult(requestCode, resultCode, intent); + IPlugin callback = this.activityResultCallback; + if (callback != null) { + callback.onActivityResult(requestCode, resultCode, intent); + } } @Override public void setActivityResultCallback(IPlugin plugin) { - this.activityResultCallback = plugin; + this.activityResultCallback = plugin; } /** * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable). * The errorCode parameter corresponds to one of the ERROR_* constants. * - * @param errorCode The error code corresponding to an ERROR_* value. - * @param description A String describing the error. - * @param failingUrl The url that failed to load. + * @param errorCode The error code corresponding to an ERROR_* value. + * @param description A String describing the error. + * @param failingUrl The url that failed to load. */ public void onReceivedError(int errorCode, String description, String failingUrl) { - final DroidGap me = this; + final DroidGap me = this; - // If errorUrl specified, then load it - final String errorUrl = me.getStringProperty("errorUrl", null); - if ((errorUrl != null) && errorUrl.startsWith("file://") && (!failingUrl.equals(errorUrl))) { + // If errorUrl specified, then load it + final String errorUrl = me.getStringProperty("errorUrl", null); + if ((errorUrl != null) && errorUrl.startsWith("file://") && (!failingUrl.equals(errorUrl))) { - // Load URL on UI thread - me.runOnUiThread(new Runnable() { - public void run() { - me.appView.loadUrl(errorUrl); - } - }); - } + // Load URL on UI thread + me.runOnUiThread(new Runnable() { + public void run() { + me.appView.loadUrl(errorUrl); + } + }); + } - // If not, then display error dialog - else { - me.appView.loadUrl("about:blank"); - me.displayError("Application Error", description + " ("+failingUrl+")", "OK", true); - } + // If not, then display error dialog + else { + me.appView.loadUrl("about:blank"); + me.displayError("Application Error", description + " ("+failingUrl+")", "OK", true); + } } /** @@ -1438,26 +1438,26 @@ public class DroidGap extends PhonegapActivity { * @param exit */ public void displayError(final String title, final String message, final String button, final boolean exit) { - final DroidGap me = this; - me.runOnUiThread(new Runnable() { - public void run() { - AlertDialog.Builder dlg = new AlertDialog.Builder(me); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(false); - dlg.setPositiveButton(button, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - if (exit) { - me.finish(); - } - } - }); - dlg.create(); - dlg.show(); - } - }); + final DroidGap me = this; + me.runOnUiThread(new Runnable() { + public void run() { + AlertDialog.Builder dlg = new AlertDialog.Builder(me); + dlg.setMessage(message); + dlg.setTitle(title); + dlg.setCancelable(false); + dlg.setPositiveButton(button, + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + if (exit) { + me.finish(); + } + } + }); + dlg.create(); + dlg.show(); + } + }); } /** @@ -1466,77 +1466,77 @@ public class DroidGap extends PhonegapActivity { */ class LinearLayoutSoftKeyboardDetect extends LinearLayout { - private static final String LOG_TAG = "SoftKeyboardDetect"; - - private int oldHeight = 0; // Need to save the old height as not to send redundant events - private int oldWidth = 0; // Need to save old width for orientation change - private int screenWidth = 0; - private int screenHeight = 0; - - public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { - super(context); - screenWidth = width; - screenHeight = height; - } + private static final String LOG_TAG = "SoftKeyboardDetect"; + + private int oldHeight = 0; // Need to save the old height as not to send redundant events + private int oldWidth = 0; // Need to save old width for orientation change + private int screenWidth = 0; + private int screenHeight = 0; + + public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { + super(context); + screenWidth = width; + screenHeight = height; + } - @Override - /** - * Start listening to new measurement events. Fire events when the height - * gets smaller fire a show keyboard event and when height gets bigger fire - * a hide keyboard event. - * - * Note: We are using callbackServer.sendJavascript() instead of - * this.appView.loadUrl() as changing the URL of the app would cause the - * soft keyboard to go away. - * - * @param widthMeasureSpec - * @param heightMeasureSpec - */ - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - Log.d(LOG_TAG, "We are in our onMeasure method"); + @Override + /** + * Start listening to new measurement events. Fire events when the height + * gets smaller fire a show keyboard event and when height gets bigger fire + * a hide keyboard event. + * + * Note: We are using callbackServer.sendJavascript() instead of + * this.appView.loadUrl() as changing the URL of the app would cause the + * soft keyboard to go away. + * + * @param widthMeasureSpec + * @param heightMeasureSpec + */ + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + Log.d(LOG_TAG, "We are in our onMeasure method"); - // Get the current height of the visible part of the screen. - // This height will not included the status bar. - int height = MeasureSpec.getSize(heightMeasureSpec); - int width = MeasureSpec.getSize(widthMeasureSpec); - - Log.d(LOG_TAG, "Old Height = " + oldHeight); - Log.d(LOG_TAG, "Height = " + height); - Log.d(LOG_TAG, "Old Width = " + oldWidth); - Log.d(LOG_TAG, "Width = " + width); - - - // If the oldHeight = 0 then this is the first measure event as the app starts up. - // If oldHeight == height then we got a measurement change that doesn't affect us. - if (oldHeight == 0 || oldHeight == height) { - Log.d(LOG_TAG, "Ignore this event"); - } - // Account for orientation change and ignore this event/Fire orientation change - else if(screenHeight == width) - { - int tmp_var = screenHeight; - screenHeight = screenWidth; - screenWidth = tmp_var; - Log.d(LOG_TAG, "Orientation Change"); - } - // If the height as gotten bigger then we will assume the soft keyboard has - // gone away. - else if (height > oldHeight) { - Log.d(LOG_TAG, "Throw hide keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');"); - } - // If the height as gotten smaller then we will assume the soft keyboard has - // been displayed. - else if (height < oldHeight) { - Log.d(LOG_TAG, "Throw show keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');"); - } + // Get the current height of the visible part of the screen. + // This height will not included the status bar. + int height = MeasureSpec.getSize(heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + + Log.d(LOG_TAG, "Old Height = " + oldHeight); + Log.d(LOG_TAG, "Height = " + height); + Log.d(LOG_TAG, "Old Width = " + oldWidth); + Log.d(LOG_TAG, "Width = " + width); + + + // If the oldHeight = 0 then this is the first measure event as the app starts up. + // If oldHeight == height then we got a measurement change that doesn't affect us. + if (oldHeight == 0 || oldHeight == height) { + Log.d(LOG_TAG, "Ignore this event"); + } + // Account for orientation change and ignore this event/Fire orientation change + else if(screenHeight == width) + { + int tmp_var = screenHeight; + screenHeight = screenWidth; + screenWidth = tmp_var; + Log.d(LOG_TAG, "Orientation Change"); + } + // If the height as gotten bigger then we will assume the soft keyboard has + // gone away. + else if (height > oldHeight) { + Log.d(LOG_TAG, "Throw hide keyboard event"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');"); + } + // If the height as gotten smaller then we will assume the soft keyboard has + // been displayed. + else if (height < oldHeight) { + Log.d(LOG_TAG, "Throw show keyboard event"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');"); + } - // Update the old height for the next event - oldHeight = height; - oldWidth = width; - } + // Update the old height for the next event + oldHeight = height; + oldWidth = width; + } } -} +} \ No newline at end of file diff --git a/framework/src/com/phonegap/ExifHelper.java b/framework/src/com/phonegap/ExifHelper.java new file mode 100644 index 00000000..cd9e0a0c --- /dev/null +++ b/framework/src/com/phonegap/ExifHelper.java @@ -0,0 +1,153 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2011, IBM Corporation + */ +package com.phonegap; + +import java.io.IOException; + +import android.media.ExifInterface; + +public class ExifHelper { + private String aperature = null; + private String datetime = null; + private String exposureTime = null; + private String flash = null; + private String focalLength = null; + private String gpsAltitude = null; + private String gpsAltitudeRef = null; + private String gpsDateStamp = null; + private String gpsLatitude = null; + private String gpsLatitudeRef = null; + private String gpsLongitude = null; + private String gpsLongitudeRef = null; + private String gpsProcessingMethod = null; + private String gpsTimestamp = null; + private String iso = null; + private String make = null; + private String model = null; + private String orientation = null; + private String whiteBalance = null; + + private ExifInterface inFile = null; + private ExifInterface outFile = null; + + /** + * The file before it is compressed + * + * @param filePath + * @throws IOException + */ + public void createInFile(String filePath) throws IOException { + this.inFile = new ExifInterface(filePath); + } + + /** + * The file after it has been compressed + * + * @param filePath + * @throws IOException + */ + public void createOutFile(String filePath) throws IOException { + this.outFile = new ExifInterface(filePath); + } + + /** + * Reads all the EXIF data from the input file. + */ + public void readExifData() { + this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE); + this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME); + this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME); + this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH); + this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH); + this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE); + this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF); + this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP); + this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE); + this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF); + this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE); + this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF); + this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD); + this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP); + this.iso = inFile.getAttribute(ExifInterface.TAG_ISO); + this.make = inFile.getAttribute(ExifInterface.TAG_MAKE); + this.model = inFile.getAttribute(ExifInterface.TAG_MODEL); + this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION); + this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE); + } + + /** + * Writes the previously stored EXIF data to the output file. + * + * @throws IOException + */ + public void writeExifData() throws IOException { + // Don't try to write to a null file + if (this.outFile == null) { + return; + } + + if (this.aperature != null) { + this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature); + } + if (this.datetime != null) { + this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime); + } + if (this.exposureTime != null) { + this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime); + } + if (this.flash != null) { + this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash); + } + if (this.focalLength != null) { + this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength); + } + if (this.gpsAltitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude); + } + if (this.gpsAltitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef); + } + if (this.gpsDateStamp != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp); + } + if (this.gpsLatitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude); + } + if (this.gpsLatitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef); + } + if (this.gpsLongitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude); + } + if (this.gpsLongitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef); + } + if (this.gpsProcessingMethod != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod); + } + if (this.gpsTimestamp != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp); + } + if (this.iso != null) { + this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso); + } + if (this.make != null) { + this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make); + } + if (this.model != null) { + this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model); + } + if (this.orientation != null) { + this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation); + } + if (this.whiteBalance != null) { + this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance); + } + + this.outFile.saveAttributes(); + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java index 240a507e..6d6ad655 100755 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -25,6 +25,7 @@ import android.provider.MediaStore; import android.util.Log; import android.webkit.MimeTypeMap; +import com.phonegap.api.PhonegapActivity; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; import com.phonegap.file.EncodingException; @@ -39,7 +40,8 @@ import com.phonegap.file.TypeMismatchException; */ public class FileUtils extends Plugin { private static final String LOG_TAG = "FileUtils"; - + private static final String _DATA = "_data"; // The column name where the file path is stored + public static int NOT_FOUND_ERR = 1; public static int SECURITY_ERR = 2; public static int ABORT_ERR = 3; @@ -988,5 +990,19 @@ public class FileUtils extends Plugin { return new FileInputStream(path); } } + + /** + * Queries the media store to find out what the file path is for the Uri we supply + * + * @param contentUri the Uri of the audio/image/video + * @param ctx the current applicaiton context + * @return the full path to the file + */ + protected static String getRealPathFromURI(Uri contentUri, PhonegapActivity ctx) { + String[] proj = { _DATA }; + Cursor cursor = ctx.managedQuery(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(_DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } } - From 8a5dec8d8b8610fc09c351be44d70a77cefb3d7e Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 22 Aug 2011 16:22:22 -0500 Subject: [PATCH 11/13] Re-checkin commit for "Fix Issue #203: Prompt crashes on Android 3.2 tablet." --- framework/src/com/phonegap/DroidGap.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index fc210341..0b6b4d50 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -914,13 +914,13 @@ public class DroidGap extends PhonegapActivity { } // Polling for JavaScript messages - else if (reqOk && defaultValue.equals("gap_poll:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { String r = callbackServer.getJavascript(); result.confirm(r); } // Calling into CallbackServer - else if (reqOk && defaultValue.equals("gap_callbackServer:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { String r = ""; if (message.equals("usePolling")) { r = ""+callbackServer.usePolling(); @@ -939,7 +939,7 @@ public class DroidGap extends PhonegapActivity { // PhoneGap JS has initialized, so show webview // (This solves white flash seen when rendering HTML) - else if (reqOk && defaultValue.equals("gap_init:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) { appView.setVisibility(View.VISIBLE); ctx.spinnerStop(); result.confirm("OK"); From facb752cc7a43d83354026ab4cb6f6501a433fbf Mon Sep 17 00:00:00 2001 From: macdonst Date: Fri, 26 Aug 2011 00:14:50 +0800 Subject: [PATCH 12/13] Fix for Issue #208: Media.release() accidentally makes a call to the Media error callback --- framework/src/com/phonegap/AudioPlayer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/com/phonegap/AudioPlayer.java b/framework/src/com/phonegap/AudioPlayer.java index df90795a..0f8c0606 100755 --- a/framework/src/com/phonegap/AudioPlayer.java +++ b/framework/src/com/phonegap/AudioPlayer.java @@ -86,7 +86,10 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On // Stop any play or record if (this.mPlayer != null) { - this.stopPlaying(); + if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { + this.mPlayer.stop(); + this.setState(MEDIA_STOPPED); + } this.mPlayer.release(); this.mPlayer = null; } From 8d35b1aeef40b400d677283866d695fc7639cce9 Mon Sep 17 00:00:00 2001 From: macdonst Date: Sat, 27 Aug 2011 04:04:57 +0800 Subject: [PATCH 13/13] Fix for Issue #210: devready event never fires if we can't get network connection info --- framework/assets/js/network.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/framework/assets/js/network.js b/framework/assets/js/network.js index f71bad50..f174b6da 100755 --- a/framework/assets/js/network.js +++ b/framework/assets/js/network.js @@ -47,6 +47,12 @@ var Connection = function() { } }, function(e) { + // If we can't get the network info we should still tell PhoneGap + // to fire the deviceready event. + if (me._firstRun) { + me._firstRun = false; + PhoneGap.onPhoneGapConnectionReady.fire(); + } console.log("Error initializing Network Connection: " + e); }); };