diff --git a/README.md b/README.md index b2198e93..49478b65 100755 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ Requires - Android SDK [http://developer.android.com](http://developer.android.com) - Apache Commons Codec [http://commons.apache.org/codec/](http://commons.apache.org/codec/) +Test Requirements +--- +- JUnit - [https://github.com/KentBeck/junit](https://github.com/KentBeck/junit) + Building --- @@ -98,6 +102,9 @@ Importing a Cordova Android Project into Eclipse 5. Right click on the project root: Run as > Run Configurations 6. Click on the Target tab and select Manual (this way you can choose the emulator or device to build to) +Running Tests +---- +Please see details under test/README.md. Further Reading --- diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js index 3e59432e..16dab290 100644 --- a/framework/assets/js/cordova.android.js +++ b/framework/assets/js/cordova.android.js @@ -1,6 +1,6 @@ -// commit ac0a3990438f4a89faa993316fb5614f61cf3be6 +// commit 347de1a785b7ecbe86c2189343ab2549a9297f9b -// File generated at :: Tue Jun 05 2012 14:14:16 GMT-0700 (PDT) +// File generated at :: Fri Jun 08 2012 16:17:50 GMT-0700 (PDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -1067,6 +1067,28 @@ module.exports = { cordova.addDocumentEventHandler('menubutton'); cordova.addDocumentEventHandler('searchbutton'); + function bindButtonChannel(buttonName) { + // generic button bind used for volumeup/volumedown buttons + return cordova.addDocumentEventHandler(buttonName + 'button', { + onSubscribe:function() { + // If we just attached the first handler, let native know we need to override the button. + if (this.numHandlers === 1) { + exec(null, null, "App", "overrideButton", [buttonName, true]); + } + }, + onUnsubscribe:function() { + // If we just detached the last handler, let native know we no longer override the volumeup button. + if (this.numHandlers === 0) { + exec(null, null, "App", "overrideButton", [buttonName, false]); + } + } + }); + + } + // Inject a listener for the volume buttons on the document. + var volumeUpButtonChannel = bindButtonChannel('volumeup'); + var volumeDownButtonChannel = bindButtonChannel('volumedown'); + // Figure out if we need to shim-in localStorage and WebSQL // support from the native side. var storage = require('cordova/plugin/android/storage'); @@ -1284,6 +1306,10 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) { exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions]); }; +cameraExport.cleanup = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, "Camera", "cleanup", []); +} + module.exports = cameraExport; }); @@ -2591,6 +2617,8 @@ var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); var FileSystem = function(name, root) { this.name = name || null; if (root) { + console.log('root.name ' + name); + console.log('root.root ' + root); this.root = new DirectoryEntry(root.name, root.fullPath); } }; @@ -3637,6 +3665,21 @@ module.exports = { exec(null, null, "App", "overrideBackbutton", [override]); }, + /** + * Override the default behavior of the Android volume button. + * If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired. + * + * Note: The user should not have to call this method. Instead, when the user + * registers for the "volume[up|down]button" event, this is automatically done. + * + * @param button volumeup, volumedown + * @param override T=override, F=cancel override + */ + overrideButton:function(button, override) { + exec(null, null, "App", "overrideButton", [button, override]); + }, + + /** * Exit and terminate the application. */ diff --git a/framework/res/xml/cordova.xml b/framework/res/xml/cordova.xml index 0eb8d5e5..4016429d 100644 --- a/framework/res/xml/cordova.xml +++ b/framework/res/xml/cordova.xml @@ -30,7 +30,7 @@ - + diff --git a/framework/src/com/phonegap/api/PluginManager.java b/framework/src/com/phonegap/api/PluginManager.java index 7781901c..0497e7d6 100755 --- a/framework/src/com/phonegap/api/PluginManager.java +++ b/framework/src/com/phonegap/api/PluginManager.java @@ -18,6 +18,7 @@ */ package com.phonegap.api; +import org.apache.cordova.CordovaWebView; import org.apache.cordova.api.CordovaInterface; import android.webkit.WebView; @@ -30,7 +31,7 @@ import android.webkit.WebView; */ public class PluginManager extends org.apache.cordova.api.PluginManager { - public PluginManager(WebView app, CordovaInterface ctx) { - super(app, ctx); + public PluginManager(WebView app, CordovaInterface ctx) throws Exception { + super((CordovaWebView) app, ctx); } } diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index 6422dbb5..a7d448e0 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -26,7 +26,6 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -43,14 +42,14 @@ public class AccelListener extends Plugin implements SensorEventListener { public static int STARTING = 1; public static int RUNNING = 2; public static int ERROR_FAILED_TO_START = 3; - - private float x,y,z; // most recent acceleration values - private long timestamp; // time of most recent value - private int status; // status of listener + + private float x,y,z; // most recent acceleration values + private long timestamp; // time of most recent value + private int status; // status of listener private int accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE; private SensorManager sensorManager; // Sensor manager - private Sensor mSensor; // Acceleration sensor returned by sensor manager + private Sensor mSensor; // Acceleration sensor returned by sensor manager private String callbackId; // Keeps track of the single "start" callback ID passed in from JS @@ -71,18 +70,19 @@ public class AccelListener extends Plugin implements SensorEventListener { * * @param ctx The context of the main Activity. */ + public void setContext(CordovaInterface ctx) { super.setContext(ctx); - this.sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE); + this.sensorManager = (SensorManager) ctx.getActivity().getSystemService(Context.SENSOR_SERVICE); } /** * 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. + * + * @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.NO_RESULT; @@ -123,9 +123,9 @@ public class AccelListener extends Plugin implements SensorEventListener { // /** * Start listening for acceleration sensor. - * - * @return status of listener - */ + * + * @return status of listener + */ private int start() { // If already starting or running, then just return if ((this.status == AccelListener.RUNNING) || (this.status == AccelListener.STARTING)) { @@ -139,24 +139,24 @@ public class AccelListener extends Plugin implements SensorEventListener { // If found, then register as listener if ((list != null) && (list.size() > 0)) { - this.mSensor = list.get(0); - this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_UI); - this.setStatus(AccelListener.STARTING); + this.mSensor = list.get(0); + this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_UI); + this.setStatus(AccelListener.STARTING); } else { - this.setStatus(AccelListener.ERROR_FAILED_TO_START); - this.fail(AccelListener.ERROR_FAILED_TO_START, "No sensors found to register accelerometer listening to."); - return this.status; + this.setStatus(AccelListener.ERROR_FAILED_TO_START); + this.fail(AccelListener.ERROR_FAILED_TO_START, "No sensors found to register accelerometer listening to."); + return this.status; } - + // Wait until running long timeout = 2000; while ((this.status == STARTING) && (timeout > 0)) { - timeout = timeout - 100; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } + timeout = timeout - 100; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } } if (timeout == 0) { this.setStatus(AccelListener.ERROR_FAILED_TO_START); @@ -210,7 +210,6 @@ public class AccelListener extends Plugin implements SensorEventListener { if (this.status == AccelListener.STOPPED) { return; } - this.setStatus(AccelListener.RUNNING); if (this.accuracy >= SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM) { @@ -252,7 +251,6 @@ public class AccelListener extends Plugin implements SensorEventListener { private void setStatus(int status) { this.status = status; } - private JSONObject getAccelerationJSON() { JSONObject r = new JSONObject(); try { diff --git a/framework/src/org/apache/cordova/App.java b/framework/src/org/apache/cordova/App.java index 5b639216..ed9ec779 100755 --- a/framework/src/org/apache/cordova/App.java +++ b/framework/src/org/apache/cordova/App.java @@ -48,6 +48,13 @@ public class App extends Plugin { if (action.equals("clearCache")) { this.clearCache(); } + else if (action.equals("show")) { // TODO @bc - Not in master branch. When should this be called? + ctx.getActivity().runOnUiThread(new Runnable() { + public void run() { + webView.postMessage("spinner", "stop"); + } + }); + } else if (action.equals("loadUrl")) { this.loadUrl(args.getString(0), args.optJSONObject(1)); } @@ -60,6 +67,9 @@ public class App extends Plugin { else if (action.equals("backHistory")) { this.backHistory(); } + else if (action.equals("overrideButton")) { + this.overrideButton(args.getString(0), args.getBoolean(1)); + } else if (action.equals("overrideBackbutton")) { this.overrideBackbutton(args.getBoolean(0)); } @@ -84,7 +94,7 @@ public class App extends Plugin { * Clear the resource cache. */ public void clearCache() { - ((DroidGap)this.ctx).clearCache(); + this.webView.clearCache(true); } /** @@ -104,7 +114,7 @@ public class App extends Plugin { HashMap params = new HashMap(); if (props != null) { JSONArray keys = props.names(); - for (int i=0; i players; // Audio player object + HashMap players; // Audio player object ArrayList pausedForPhone; // Audio players that were paused when phone call came in /** * Constructor. */ public AudioHandler() { - this.players = new HashMap(); + this.players = new HashMap(); this.pausedForPhone = new ArrayList(); } /** * 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. @@ -111,7 +109,6 @@ public class AudioHandler extends Plugin { /** * Identifies if action to be executed returns a value and should be run synchronously. - * * @param action The action to execute * @return T=returns value */ @@ -140,8 +137,9 @@ public class AudioHandler extends Plugin { * * @param id The message id * @param data The message data + * @return Object to stop propagation or null */ - public void onMessage(String id, Object data) { + public Object onMessage(String id, Object data) { // If phone message if (id.equals("telephone")) { @@ -167,6 +165,7 @@ public class AudioHandler extends Plugin { this.pausedForPhone.clear(); } } + return null; } //-------------------------------------------------------------------------- @@ -175,7 +174,6 @@ public class AudioHandler extends Plugin { /** * Release the audio player instance to save memory. - * * @param id The id of the audio player */ private boolean release(String id) { @@ -190,7 +188,6 @@ public class AudioHandler extends Plugin { /** * Start recording and save the specified file. - * * @param id The id of the audio player * @param file The name of the file */ @@ -206,7 +203,6 @@ public class AudioHandler extends Plugin { /** * Stop recording and save to the file specified when recording started. - * * @param id The id of the audio player */ public void stopRecordingAudio(String id) { @@ -219,7 +215,6 @@ public class AudioHandler extends Plugin { /** * Start or resume playing audio file. - * * @param id The id of the audio player * @param file The name of the audio file. */ @@ -234,8 +229,6 @@ public class AudioHandler extends Plugin { /** * Seek to a location. - * - * * @param id The id of the audio player * @param miliseconds int: number of milliseconds to skip 1000 = 1 second */ @@ -248,7 +241,6 @@ public class AudioHandler extends Plugin { /** * Pause playing. - * * @param id The id of the audio player */ public void pausePlayingAudio(String id) { @@ -260,7 +252,6 @@ public class AudioHandler extends Plugin { /** * Stop playing the audio file. - * * @param id The id of the audio player */ public void stopPlayingAudio(String id) { @@ -274,21 +265,19 @@ public class AudioHandler extends Plugin { /** * Get current position of playback. - * * @param id The id of the audio player * @return position in msec */ public float getCurrentPositionAudio(String id) { AudioPlayer audio = this.players.get(id); if (audio != null) { - return(audio.getCurrentPosition()/1000.0f); + return (audio.getCurrentPosition() / 1000.0f); } return -1; } /** * Get the duration of the audio file. - * * @param id The id of the audio player * @param file The name of the audio file. * @return The duration in msec. @@ -298,14 +287,14 @@ public class AudioHandler extends Plugin { // Get audio file AudioPlayer audio = this.players.get(id); if (audio != null) { - return(audio.getDuration(file)); + return (audio.getDuration(file)); } // If not already open, then open the file else { audio = new AudioPlayer(this, id); this.players.put(id, audio); - return(audio.getDuration(file)); + return (audio.getDuration(file)); } } @@ -314,8 +303,9 @@ public class AudioHandler extends Plugin { * * @param output 1=earpiece, 2=speaker */ + @SuppressWarnings("deprecation") public void setAudioOutputDevice(int output) { - AudioManager audiMgr = (AudioManager) this.ctx.getSystemService(Context.AUDIO_SERVICE); + AudioManager audiMgr = (AudioManager) this.ctx.getActivity().getSystemService(Context.AUDIO_SERVICE); if (output == 2) { audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_SPEAKER, AudioManager.ROUTE_ALL); } @@ -332,8 +322,9 @@ public class AudioHandler extends Plugin { * * @return 1=earpiece, 2=speaker */ + @SuppressWarnings("deprecation") public int getAudioOutputDevice() { - AudioManager audiMgr = (AudioManager) this.ctx.getSystemService(Context.AUDIO_SERVICE); + AudioManager audiMgr = (AudioManager) this.ctx.getActivity().getSystemService(Context.AUDIO_SERVICE); if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_EARPIECE) { return 1; } diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java index 675161fe..e975afaf 100755 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -37,8 +37,8 @@ import java.io.IOException; * Only one file can be played or recorded per class instance. * * Local audio files must reside in one of two places: - * android_asset: file name must start with /android_asset/sound.mp3 - * sdcard: file name is just sound.mp3 + * android_asset: file name must start with /android_asset/sound.mp3 + * sdcard: file name is just sound.mp3 */ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener { @@ -63,24 +63,24 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On private static int MEDIA_ERR_NETWORK = 2; private static int MEDIA_ERR_DECODE = 3; private static int MEDIA_ERR_NONE_SUPPORTED = 4; + + private AudioHandler handler; // The AudioHandler object + private String id; // The id of this player (used to identify Media object in JavaScript) + private int state = MEDIA_NONE; // State of recording or playback + private String audioFile = null; // File name to play or record to + private float duration = -1; // Duration of audio - private AudioHandler handler; // The AudioHandler object - private String id; // The id of this player (used to identify Media object in JavaScript) - private int state = MEDIA_NONE; // State of recording or playback - private String audioFile = null; // File name to play or record to - private float duration = -1; // Duration of audio - - private MediaRecorder recorder = null; // Audio recording object - private String tempFile = null; // Temporary recording file name - - private MediaPlayer mPlayer = null; // Audio player object - private boolean prepareOnly = false; + private MediaRecorder recorder = null; // Audio recording object + private String tempFile = null; // Temporary recording file name + + private MediaPlayer mPlayer = null; // Audio player object + private boolean prepareOnly = false; /** * Constructor. - * - * @param handler The audio handler object - * @param id The id of this audio player + * + * @param handler The audio handler object + * @param id The id of this audio player */ public AudioPlayer(AudioHandler handler, String id) { this.handler = handler; @@ -88,7 +88,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3"; } else { - this.tempFile = "/data/data/" + handler.ctx.getPackageName() + "/cache/tmprecording.mp3"; + this.tempFile = "/data/data/" + handler.ctx.getActivity().getPackageName() + "/cache/tmprecording.mp3"; } } @@ -96,7 +96,6 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * Destroy player and stop audio playing or recording. */ public void destroy() { - // Stop any play or record if (this.mPlayer != null) { if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { @@ -115,15 +114,14 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On /** * Start recording the specified file. - * - * @param file The name of the file + * + * @param file The name of the file */ public void startRecording(String file) { if (this.mPlayer != null) { Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode."); this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); } - // Make sure we're not already recording else if (this.recorder == null) { this.audioFile = file; @@ -162,7 +160,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On f.renameTo(new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + file)); } else { - f.renameTo(new File("/data/data/" + handler.ctx.getPackageName() + "/cache/" + file)); + f.renameTo(new File("/data/data/" + handler.ctx.getActivity().getPackageName() + "/cache/" + file)); } } @@ -187,13 +185,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On /** * Start or resume playing audio file. - * - * @param file The name of the audio file. + * + * @param file The name of the audio file. */ public void startPlaying(String file) { if (this.recorder != null) { Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode."); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); } // If this is a new request to play audio, or stopped @@ -222,7 +220,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On else { if (file.startsWith("/android_asset/")) { String f = file.substring(15); - android.content.res.AssetFileDescriptor fd = this.handler.ctx.getBaseContext().getAssets().openFd(f); + android.content.res.AssetFileDescriptor fd = this.handler.ctx.getActivity().getAssets().openFd(f); this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); } else { @@ -242,10 +240,9 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On // Get duration this.duration = getDurationInSeconds(); } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); } } @@ -258,8 +255,8 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.setState(MEDIA_RUNNING); } else { - Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: "+this.state); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); + Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); } } } @@ -271,7 +268,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On if (this.mPlayer != null) { this.mPlayer.seekTo(milliseconds); Log.d(LOG_TAG, "Send a onStatus update for the new seek"); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_POSITION+", "+milliseconds/1000.0f+");"); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");"); } } @@ -286,8 +283,8 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.setState(MEDIA_PAUSED); } else { - Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: "+this.state); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_NONE_ACTIVE+"});"); + Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});"); } } @@ -300,15 +297,15 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.setState(MEDIA_STOPPED); } else { - Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: "+this.state); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_NONE_ACTIVE+"});"); + Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});"); } } /** * Callback to be invoked when playback of a media source has completed. - * - * @param mPlayer The MediaPlayer that reached the end of the file + * + * @param mPlayer The MediaPlayer that reached the end of the file */ public void onCompletion(MediaPlayer mPlayer) { this.setState(MEDIA_STOPPED); @@ -316,13 +313,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On /** * Get current position of playback. - * - * @return position in msec or -1 if not playing + * + * @return position in msec or -1 if not playing */ public long getCurrentPosition() { if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { int curPos = this.mPlayer.getCurrentPosition(); - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_POSITION+", "+curPos/1000.0f+");"); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");"); return curPos; } else { @@ -333,9 +330,9 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On /** * Determine if playback file is streaming or local. * It is streaming if file name starts with "http://" - * - * @param file The file name - * @return T=streaming, F=local + * + * @param file The file name + * @return T=streaming, F=local */ public boolean isStreaming(String file) { if (file.contains("http://") || file.contains("https://")) { @@ -346,19 +343,19 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On } } - /** - * Get the duration of the audio file. - * - * @param file The name of the audio file. - * @return The duration in msec. - * -1=can't be determined - * -2=not allowed - */ + /** + * Get the duration of the audio file. + * + * @param file The name of the audio file. + * @return The duration in msec. + * -1=can't be determined + * -2=not allowed + */ public float getDuration(String file) { // Can't get duration of recording if (this.recorder != null) { - return(-2); // not allowed + return (-2); // not allowed } // If audio file already loaded and started, then return duration @@ -378,9 +375,9 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On } /** - * Callback to be invoked when the media source is ready for playback. - * - * @param mPlayer The MediaPlayer that is ready for playback + * Callback to be invoked when the media source is ready for playback. + * + * @param mPlayer The MediaPlayer that is ready for playback */ public void onPrepared(MediaPlayer mPlayer) { // Listen for playback completion @@ -401,13 +398,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.prepareOnly = false; // Send status notification to JavaScript - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_DURATION+","+this.duration+");"); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");"); } /** * By default Android returns the length of audio in mills but we want seconds - * + * * @return length of clip in seconds */ private float getDurationInSeconds() { @@ -417,31 +414,31 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On /** * Callback to be invoked when there has been an error during an asynchronous operation * (other errors will throw exceptions at method call time). - * - * @param mPlayer the MediaPlayer the error pertains to - * @param arg1 the type of error that has occurred: (MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_SERVER_DIED) - * @param arg2 an extra code, specific to the error. + * + * @param mPlayer the MediaPlayer the error pertains to + * @param arg1 the type of error that has occurred: (MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_SERVER_DIED) + * @param arg2 an extra code, specific to the error. */ public boolean onError(MediaPlayer mPlayer, int arg1, int arg2) { - Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2+")"); + Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2 + ")"); // TODO: Not sure if this needs to be sent? this.mPlayer.stop(); this.mPlayer.release(); // Send error notification to JavaScript - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":"+arg1+"});"); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});"); return false; } /** * Set the state and send it to JavaScript. - * + * * @param state */ private void setState(int state) { if (this.state != state) { - this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_STATE+", "+state+");"); + this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state + ");"); } this.state = state; @@ -449,7 +446,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On /** * Get the audio state. - * + * * @return int */ public int getState() { diff --git a/framework/src/org/apache/cordova/BatteryListener.java b/framework/src/org/apache/cordova/BatteryListener.java index 22b1b94b..bca96332 100755 --- a/framework/src/org/apache/cordova/BatteryListener.java +++ b/framework/src/org/apache/cordova/BatteryListener.java @@ -24,7 +24,6 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -65,7 +64,7 @@ public class BatteryListener extends Plugin { this.batteryCallbackId = callbackId; // We need to listen to power events to update battery status - IntentFilter intentFilter = new IntentFilter() ; + IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); if (this.receiver == null) { this.receiver = new BroadcastReceiver() { @@ -74,7 +73,7 @@ public class BatteryListener extends Plugin { updateBatteryInfo(intent); } }; - ctx.registerReceiver(this.receiver, intentFilter); + ctx.getActivity().registerReceiver(this.receiver, intentFilter); } // Don't return any result now, since status results will be sent when events come in from broadcast receiver @@ -106,7 +105,7 @@ public class BatteryListener extends Plugin { private void removeBatteryListener() { if (this.receiver != null) { try { - this.ctx.unregisterReceiver(this.receiver); + this.ctx.getActivity().unregisterReceiver(this.receiver); this.receiver = null; } catch (Exception e) { Log.e(LOG_TAG, "Error unregistering battery receiver: " + e.getMessage(), e); diff --git a/framework/src/org/apache/cordova/CallbackServer.java b/framework/src/org/apache/cordova/CallbackServer.java index a34ef3cb..2eb292f8 100755 --- a/framework/src/org/apache/cordova/CallbackServer.java +++ b/framework/src/org/apache/cordova/CallbackServer.java @@ -53,6 +53,7 @@ import java.util.LinkedList; */ public class CallbackServer implements Runnable { + @SuppressWarnings("unused") private static final String LOG_TAG = "CallbackServer"; /** @@ -94,7 +95,7 @@ public class CallbackServer implements Runnable { * Constructor. */ public CallbackServer() { - //System.out.println("CallbackServer()"); + //Log.d(LOG_TAG, "CallbackServer()"); this.active = false; this.empty = true; this.port = 0; @@ -103,7 +104,7 @@ public class CallbackServer implements Runnable { /** * Init callback server and start XHR if running local app. - * + * * If Cordova app is loaded from file://, then we can use XHR * otherwise we have to use polling due to cross-domain security restrictions. * @@ -143,7 +144,6 @@ public class CallbackServer implements Runnable { /** * Return if polling is being used instead of XHR. - * * @return */ public boolean usePolling() { @@ -152,7 +152,6 @@ public class CallbackServer implements Runnable { /** * Get the port that this server is running on. - * * @return */ public int getPort() { @@ -161,7 +160,6 @@ public class CallbackServer implements Runnable { /** * Get the security token that this server requires when calling getJavascript(). - * * @return */ public String getToken() { @@ -172,7 +170,7 @@ public class CallbackServer implements Runnable { * Start the server on a new thread. */ public void startServer() { - //System.out.println("CallbackServer.startServer()"); + //Log.d(LOG_TAG, "CallbackServer.startServer()"); this.active = false; // Start server on new thread @@ -193,7 +191,7 @@ public class CallbackServer implements Runnable { } /** - * Start running the server. + * Start running the server. * This is called automatically when the server thread is started. */ public void run() { @@ -204,90 +202,90 @@ public class CallbackServer implements Runnable { String request; ServerSocket waitSocket = new ServerSocket(0); this.port = waitSocket.getLocalPort(); - //System.out.println("CallbackServer -- using port " +this.port); + //Log.d(LOG_TAG, "CallbackServer -- using port " +this.port); this.token = java.util.UUID.randomUUID().toString(); - //System.out.println("CallbackServer -- using token "+this.token); + //Log.d(LOG_TAG, "CallbackServer -- using token "+this.token); - while (this.active) { - //System.out.println("CallbackServer: Waiting for data on socket"); - Socket connection = waitSocket.accept(); - BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()),40); - DataOutputStream output = new DataOutputStream(connection.getOutputStream()); - request = xhrReader.readLine(); - String response = ""; - //System.out.println("CallbackServerRequest="+request); - if (this.active && (request != null)) { - if (request.contains("GET")) { + while (this.active) { + //Log.d(LOG_TAG, "CallbackServer: Waiting for data on socket"); + Socket connection = waitSocket.accept(); + BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()), 40); + DataOutputStream output = new DataOutputStream(connection.getOutputStream()); + request = xhrReader.readLine(); + String response = ""; + //Log.d(LOG_TAG, "CallbackServerRequest="+request); + if (this.active && (request != null)) { + if (request.contains("GET")) { - // Get requested file - String[] requestParts = request.split(" "); + // Get requested file + String[] requestParts = request.split(" "); - // Must have security token - if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) { - //System.out.println("CallbackServer -- Processing GET request"); + // Must have security token + if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) { + //Log.d(LOG_TAG, "CallbackServer -- Processing GET request"); - // Wait until there is some data to send, or send empty data every 10 sec - // to prevent XHR timeout on the client - synchronized (this) { - while (this.empty) { - try { - this.wait(10000); // prevent timeout from happening - //System.out.println("CallbackServer>>> break <<<"); - break; - } - catch (Exception e) { } - } - } + // Wait until there is some data to send, or send empty data every 10 sec + // to prevent XHR timeout on the client + synchronized (this) { + while (this.empty) { + try { + this.wait(10000); // prevent timeout from happening + //Log.d(LOG_TAG, "CallbackServer>>> break <<<"); + break; + } catch (Exception e) { + } + } + } - // If server is still running - if (this.active) { + // If server is still running + if (this.active) { - // If no data, then send 404 back to client before it times out - if (this.empty) { - //System.out.println("CallbackServer -- sending data 0"); - response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space - } - else { - //System.out.println("CallbackServer -- sending item"); - response = "HTTP/1.1 200 OK\r\n\r\n"; - String js = this.getJavascript(); - if (js != null) { - response += encode(js, "UTF-8"); - } - } - } - else { - response = "HTTP/1.1 503 Service Unavailable\r\n\r\n "; - } - } - else { - response = "HTTP/1.1 403 Forbidden\r\n\r\n "; - } - } - else { - response = "HTTP/1.1 400 Bad Request\r\n\r\n "; - } - //System.out.println("CallbackServer: response="+response); - //System.out.println("CallbackServer: closing output"); - output.writeBytes(response); - output.flush(); - } - output.close(); - xhrReader.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - this.active = false; - //System.out.println("CallbackServer.startServer() - EXIT"); + // If no data, then send 404 back to client before it times out + if (this.empty) { + //Log.d(LOG_TAG, "CallbackServer -- sending data 0"); + response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space + } + else { + //Log.d(LOG_TAG, "CallbackServer -- sending item"); + response = "HTTP/1.1 200 OK\r\n\r\n"; + String js = this.getJavascript(); + if (js != null) { + response += encode(js, "UTF-8"); + } + } + } + else { + response = "HTTP/1.1 503 Service Unavailable\r\n\r\n "; + } + } + else { + response = "HTTP/1.1 403 Forbidden\r\n\r\n "; + } + } + else { + response = "HTTP/1.1 400 Bad Request\r\n\r\n "; + } + //Log.d(LOG_TAG, "CallbackServer: response="+response); + //Log.d(LOG_TAG, "CallbackServer: closing output"); + output.writeBytes(response); + output.flush(); + } + output.close(); + xhrReader.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + this.active = false; + //Log.d(LOG_TAG, "CallbackServer.startServer() - EXIT"); } /** - * Stop server. + * Stop server. * This stops the thread that the server is running on. */ public void stopServer() { - //System.out.println("CallbackServer.stopServer()"); + //Log.d(LOG_TAG, "CallbackServer.stopServer()"); if (this.active) { this.active = false; @@ -307,11 +305,11 @@ public class CallbackServer implements Runnable { /** * Get the number of JavaScript statements. - * + * * @return int */ public int getSize() { - synchronized(this) { + synchronized (this) { int size = this.javascript.size(); return size; } @@ -319,11 +317,11 @@ public class CallbackServer implements Runnable { /** * Get the next JavaScript statement and remove from list. - * + * * @return String */ public String getJavascript() { - synchronized(this) { + synchronized (this) { if (this.javascript.size() == 0) { return null; } @@ -337,7 +335,7 @@ public class CallbackServer implements Runnable { /** * Add a JavaScript statement to the list. - * + * * @param statement */ public void sendJavascript(String statement) { diff --git a/framework/src/org/apache/cordova/CameraLauncher.java b/framework/src/org/apache/cordova/CameraLauncher.java index cba7e49c..436c232f 100755 --- a/framework/src/org/apache/cordova/CameraLauncher.java +++ b/framework/src/org/apache/cordova/CameraLauncher.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.OutputStream; import org.apache.commons.codec.binary.Base64; +//import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.LOG; import org.apache.cordova.api.Plugin; import org.apache.cordova.api.PluginResult; @@ -34,6 +35,7 @@ import org.json.JSONException; import android.app.Activity; import android.content.ContentValues; +//import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; @@ -78,12 +80,23 @@ public class CameraLauncher extends Plugin { public String callbackId; private int numPics; + //This should never be null! + //private CordovaInterface cordova; + /** * Constructor. */ public CameraLauncher() { } +// public void setContext(CordovaInterface mCtx) { +// super.setContext(mCtx); +// if (CordovaInterface.class.isInstance(mCtx)) +// cordova = (CordovaInterface) mCtx; +// else +// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity"); +// } + /** * Executes the request and returns PluginResult. * @@ -163,7 +176,11 @@ public class CameraLauncher extends Plugin { intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); this.imageUri = Uri.fromFile(photo); - this.ctx.startActivityForResult((Plugin) this, intent, (CAMERA+1)*16 + returnType+1); + if (this.ctx != null) { + this.ctx.startActivityForResult((Plugin) this, intent, (CAMERA + 1) * 16 + returnType + 1); + } +// else +// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity"); } /** @@ -175,9 +192,9 @@ public class CameraLauncher extends Plugin { private File createCaptureFile(int encodingType) { File photo = null; if (encodingType == JPEG) { - photo = new File(DirectoryManager.getTempDirectoryPath(ctx.getContext()), "Pic.jpg"); + photo = new File(DirectoryManager.getTempDirectoryPath(this.ctx.getActivity()), "Pic.jpg"); } else if (encodingType == PNG) { - photo = new File(DirectoryManager.getTempDirectoryPath(ctx.getContext()), "Pic.png"); + photo = new File(DirectoryManager.getTempDirectoryPath(this.ctx.getActivity()), "Pic.png"); } else { throw new IllegalArgumentException("Invalid Encoding Type: " + encodingType); } @@ -211,8 +228,10 @@ public class CameraLauncher extends Plugin { intent.setAction(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); - this.ctx.startActivityForResult((Plugin) this, Intent.createChooser(intent, - new String(title)), (srcType+1)*16 + returnType + 1); + if (this.ctx != null) { + this.ctx.startActivityForResult((Plugin) this, Intent.createChooser(intent, + new String(title)), (srcType + 1) * 16 + returnType + 1); + } } /** @@ -246,8 +265,8 @@ public class CameraLauncher extends Plugin { // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this // would result in whitespace in the new image. else { - double newRatio = newWidth / (double)newHeight; - double origRatio = origWidth / (double)origHeight; + double newRatio = newWidth / (double) newHeight; + double origRatio = origWidth / (double) origHeight; if (origRatio > newRatio) { newHeight = (newWidth * origHeight) / origWidth; @@ -272,21 +291,20 @@ public class CameraLauncher extends Plugin { public void onActivityResult(int requestCode, int resultCode, Intent intent) { // Get src and dest types from request code - int srcType = (requestCode/16) - 1; + int srcType = (requestCode / 16) - 1; int destType = (requestCode % 16) - 1; - int rotate = 0; - - // Create an ExifHelper to save the exif data that is lost during compression - ExifHelper exif = new ExifHelper(); - try { - if (this.encodingType == JPEG) { - exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx.getContext()) + "/Pic.jpg"); - exif.readExifData(); - } - } catch (IOException e) { - e.printStackTrace(); - } + int rotate = 0; + // Create an ExifHelper to save the exif data that is lost during compression + ExifHelper exif = new ExifHelper(); + try { + if (this.encodingType == JPEG) { + exif.createInFile(DirectoryManager.getTempDirectoryPath(this.ctx.getActivity()) + "/Pic.jpg"); + exif.readExifData(); + } + } catch (IOException e) { + e.printStackTrace(); + } // If CAMERA if (srcType == CAMERA) { // If image available @@ -295,10 +313,10 @@ public class CameraLauncher extends Plugin { // Read in bitmap of captured image Bitmap bitmap; try { - bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getActivity().getContentResolver(), imageUri); } catch (FileNotFoundException e) { Uri uri = intent.getData(); - android.content.ContentResolver resolver = this.ctx.getContentResolver(); + android.content.ContentResolver resolver = this.ctx.getActivity().getContentResolver(); bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); } @@ -311,18 +329,18 @@ public class CameraLauncher extends Plugin { } // If sending filename back - else if (destType == FILE_URI){ + 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); + uri = this.ctx.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); } catch (UnsupportedOperationException e) { LOG.d(LOG_TAG, "Can't write to external media storage."); try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); + uri = this.ctx.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); } catch (UnsupportedOperationException ex) { LOG.d(LOG_TAG, "Can't write to internal media storage."); this.failPicture("Error capturing image - no media storage found."); @@ -331,7 +349,7 @@ public class CameraLauncher extends Plugin { } // Add compressed version of captured image to returned media store Uri - OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + OutputStream os = this.ctx.getActivity().getContentResolver().openOutputStream(uri); bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); os.close(); @@ -370,9 +388,9 @@ public class CameraLauncher extends Plugin { else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { if (resultCode == Activity.RESULT_OK) { Uri uri = intent.getData(); - android.content.ContentResolver resolver = this.ctx.getContentResolver(); + android.content.ContentResolver resolver = this.ctx.getActivity().getContentResolver(); - // If you ask for video or all media type you will automatically get back a file URI + // If you ask for video or all media type you will automatically get back a file URI // and there will be no attempt to resize any returned data if (this.mediaType != PICTURE) { this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); @@ -382,21 +400,21 @@ public class CameraLauncher extends Plugin { if (destType == DATA_URL) { try { Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); - String[] cols = { MediaStore.Images.Media.ORIENTATION }; - Cursor cursor = this.ctx.getContentResolver().query(intent.getData(), - cols, - null, null, null); - if (cursor != null) { - cursor.moveToPosition(0); - rotate = cursor.getInt(0); - cursor.close(); - } - if (rotate != 0) { - Matrix matrix = new Matrix(); - matrix.setRotate(rotate); - bitmap = bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - } - bitmap = scaleBitmap(bitmap); + String[] cols = { MediaStore.Images.Media.ORIENTATION }; + Cursor cursor = this.ctx.getActivity().getContentResolver().query(intent.getData(), + cols, + null, null, null); + if (cursor != null) { + cursor.moveToPosition(0); + rotate = cursor.getInt(0); + cursor.close(); + } + if (rotate != 0) { + Matrix matrix = new Matrix(); + matrix.setRotate(rotate); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + } + bitmap = scaleBitmap(bitmap); this.processPicture(bitmap); bitmap.recycle(); bitmap = null; @@ -415,7 +433,7 @@ public class CameraLauncher extends Plugin { Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); bitmap = scaleBitmap(bitmap); - String fileName = DirectoryManager.getTempDirectoryPath(ctx.getContext()) + "/resize.jpg"; + String fileName = DirectoryManager.getTempDirectoryPath(this.ctx.getActivity()) + "/resize.jpg"; OutputStream os = new FileOutputStream(fileName); bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); os.close(); @@ -429,7 +447,7 @@ public class CameraLauncher extends Plugin { bitmap.recycle(); bitmap = null; - // The resized image is cached by the app in order to get around this and not have to delete you + // The resized image is cached by the app in order to get around this and not have to delete you // application cache I'm adding the current system time to the end of the file url. this.success(new PluginResult(PluginResult.Status.OK, ("file://" + fileName + "?" + System.currentTimeMillis())), this.callbackId); System.gc(); @@ -459,7 +477,7 @@ public class CameraLauncher extends Plugin { * @return a cursor */ private Cursor queryImgDB() { - return this.ctx.getContentResolver().query( + return this.ctx.getActivity().getContentResolver().query( android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { MediaStore.Images.Media._ID }, null, @@ -488,7 +506,7 @@ public class CameraLauncher extends Plugin { cursor.moveToLast(); int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))) - 1; Uri uri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "/" + id); - this.ctx.getContentResolver().delete(uri, null, null); + this.ctx.getActivity().getContentResolver().delete(uri, null, null); } } @@ -501,7 +519,7 @@ public class CameraLauncher extends Plugin { ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); try { if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) { - byte[] code = jpeg_data.toByteArray(); + 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); @@ -509,8 +527,7 @@ public class CameraLauncher extends Plugin { output = null; code = null; } - } - catch(Exception e) { + } catch (Exception e) { this.failPicture("Error compressing image."); } jpeg_data = null; diff --git a/framework/src/org/apache/cordova/Capture.java b/framework/src/org/apache/cordova/Capture.java index adf70592..c6f82029 100644 --- a/framework/src/org/apache/cordova/Capture.java +++ b/framework/src/org/apache/cordova/Capture.java @@ -38,11 +38,10 @@ import android.media.MediaPlayer; import android.net.Uri; import android.util.Log; - public class Capture extends Plugin { private static final String VIDEO_3GPP = "video/3gpp"; - private static final String VIDEO_MP4 = "video/mp4"; + private static final String VIDEO_MP4 = "video/mp4"; private static final String AUDIO_3GPP = "audio/3gpp"; private static final String IMAGE_JPEG = "image/jpeg"; @@ -52,8 +51,8 @@ public class Capture extends Plugin { private static final String LOG_TAG = "Capture"; private static final int CAPTURE_INTERNAL_ERR = 0; - private static final int CAPTURE_APPLICATION_BUSY = 1; - private static final int CAPTURE_INVALID_ARGUMENT = 2; +// private static final int CAPTURE_APPLICATION_BUSY = 1; +// private static final int CAPTURE_INVALID_ARGUMENT = 2; private static final int CAPTURE_NO_MEDIA_FILES = 3; private static final int CAPTURE_NOT_SUPPORTED = 20; @@ -63,6 +62,16 @@ public class Capture extends Plugin { private JSONArray results; // The array of results to be returned to the user private Uri imageUri; // Uri of captured image + //private CordovaInterface cordova; + +// public void setContext(Context mCtx) +// { +// if (CordovaInterface.class.isInstance(mCtx)) +// cordova = (CordovaInterface) mCtx; +// else +// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity"); +// } + @Override public PluginResult execute(String action, JSONArray args, String callbackId) { this.callbackId = callbackId; @@ -132,8 +141,7 @@ public class Capture extends Plugin { else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) { obj = getAudioVideoData(filePath, obj, true); } - } - catch (JSONException e) { + } catch (JSONException e) { Log.d(LOG_TAG, "Error: setting media file data object"); } return obj; @@ -169,13 +177,12 @@ public class Capture extends Plugin { try { player.setDataSource(filePath); player.prepare(); - obj.put("duration", player.getDuration()/1000); + obj.put("duration", player.getDuration() / 1000); if (video) { obj.put("height", player.getVideoHeight()); obj.put("width", player.getVideoWidth()); } - } - catch (IOException e) { + } catch (IOException e) { Log.d(LOG_TAG, "Error: loading video file"); } return obj; @@ -197,7 +204,7 @@ public class Capture extends Plugin { Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); // Specify file so that large image is captured and returned - File photo = new File(DirectoryManager.getTempDirectoryPath(ctx.getContext()), "Capture.jpg"); + File photo = new File(DirectoryManager.getTempDirectoryPath(this.ctx.getActivity()), "Capture.jpg"); intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); this.imageUri = Uri.fromFile(photo); @@ -250,11 +257,11 @@ public class Capture extends Plugin { try { // Create an ExifHelper to save the exif data that is lost during compression ExifHelper exif = new ExifHelper(); - exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx.getContext()) + "/Capture.jpg"); + exif.createInFile(DirectoryManager.getTempDirectoryPath(this.ctx.getActivity()) + "/Capture.jpg"); exif.readExifData(); // Read in bitmap of captured image - Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getActivity().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) @@ -262,11 +269,11 @@ public class Capture extends Plugin { 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); + uri = this.ctx.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); } catch (UnsupportedOperationException e) { LOG.d(LOG_TAG, "Can't write to external media storage."); try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); + uri = this.ctx.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); } catch (UnsupportedOperationException ex) { LOG.d(LOG_TAG, "Can't write to internal media storage."); this.fail(createErrorObject(CAPTURE_INTERNAL_ERR, "Error capturing image - no media storage found.")); @@ -275,7 +282,7 @@ public class Capture extends Plugin { } // Add compressed version of captured image to returned media store Uri - OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + OutputStream os = this.ctx.getActivity().getContentResolver().openOutputStream(uri); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); os.close(); @@ -347,7 +354,7 @@ public class Capture extends Plugin { * @return a JSONObject that represents a File * @throws IOException */ - private JSONObject createMediaFile(Uri data){ + private JSONObject createMediaFile(Uri data) { File fp = new File(FileUtils.getRealPathFromURI(data, this.ctx)); JSONObject obj = new JSONObject(); @@ -355,9 +362,8 @@ public class Capture extends Plugin { // File properties obj.put("name", fp.getName()); obj.put("fullPath", "file://" + fp.getAbsolutePath()); - - // Because of an issue with MimeTypeMap.getMimeTypeFromExtension() all .3gpp files - // are reported as video/3gpp. I'm doing this hacky check of the URI to see if it + // Because of an issue with MimeTypeMap.getMimeTypeFromExtension() all .3gpp files + // are reported as video/3gpp. I'm doing this hacky check of the URI to see if it // is stored in the audio or video content store. if (fp.getAbsoluteFile().toString().endsWith(".3gp") || fp.getAbsoluteFile().toString().endsWith(".3gpp")) { if (data.toString().contains("/audio/")) { diff --git a/framework/src/org/apache/cordova/CompassListener.java b/framework/src/org/apache/cordova/CompassListener.java index 86151575..d6e54a7e 100755 --- a/framework/src/org/apache/cordova/CompassListener.java +++ b/framework/src/org/apache/cordova/CompassListener.java @@ -27,7 +27,6 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -72,7 +71,7 @@ public class CompassListener extends Plugin implements SensorEventListener { */ public void setContext(CordovaInterface ctx) { super.setContext(ctx); - this.sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE); + this.sensorManager = (SensorManager) ctx.getActivity().getSystemService(Context.SENSOR_SERVICE); } /** @@ -184,6 +183,7 @@ public class CompassListener extends Plugin implements SensorEventListener { } // Get compass sensor from sensor manager + @SuppressWarnings("deprecation") List list = this.sensorManager.getSensorList(Sensor.TYPE_ORIENTATION); // If found, then register as listener @@ -212,7 +212,6 @@ public class CompassListener extends Plugin implements SensorEventListener { this.setStatus(CompassListener.STOPPED); } - public void onAccuracyChanged(Sensor sensor, int accuracy) { // TODO Auto-generated method stub } diff --git a/framework/src/org/apache/cordova/ContactAccessor.java b/framework/src/org/apache/cordova/ContactAccessor.java index b8ac6361..44bed23f 100644 --- a/framework/src/org/apache/cordova/ContactAccessor.java +++ b/framework/src/org/apache/cordova/ContactAccessor.java @@ -22,6 +22,7 @@ import android.content.Context; import android.util.Log; import android.webkit.WebView; +import org.apache.cordova.api.CordovaInterface; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -36,7 +37,7 @@ import org.json.JSONObject; public abstract class ContactAccessor { protected final String LOG_TAG = "ContactsAccessor"; - protected Context mApp; + protected CordovaInterface mApp; protected WebView mView; /** @@ -75,7 +76,7 @@ public abstract class ContactAccessor { map.put("urls", true); map.put("photos", true); map.put("categories", true); - } + } else { for (int i=0; i@.*/ + private static final String EMAIL_REGEXP = ".+@.+\\.+.+"; /* @.*/ - /** - * A static map that converts the JavaScript property name to Android database column name. - */ + /** + * A static map that converts the JavaScript property name to Android database column name. + */ private static final Map dbMap = new HashMap(); static { - dbMap.put("id", ContactsContract.Data.CONTACT_ID); - dbMap.put("displayName", ContactsContract.Contacts.DISPLAY_NAME); - dbMap.put("name", ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); - dbMap.put("name.formatted", ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); - dbMap.put("name.familyName", ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME); - dbMap.put("name.givenName", ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME); - dbMap.put("name.middleName", ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME); - dbMap.put("name.honorificPrefix", ContactsContract.CommonDataKinds.StructuredName.PREFIX); - dbMap.put("name.honorificSuffix", ContactsContract.CommonDataKinds.StructuredName.SUFFIX); - dbMap.put("nickname", ContactsContract.CommonDataKinds.Nickname.NAME); - dbMap.put("phoneNumbers", ContactsContract.CommonDataKinds.Phone.NUMBER); - dbMap.put("phoneNumbers.value", ContactsContract.CommonDataKinds.Phone.NUMBER); - dbMap.put("emails", ContactsContract.CommonDataKinds.Email.DATA); - dbMap.put("emails.value", ContactsContract.CommonDataKinds.Email.DATA); - dbMap.put("addresses", ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); - dbMap.put("addresses.formatted", ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); - dbMap.put("addresses.streetAddress", ContactsContract.CommonDataKinds.StructuredPostal.STREET); - dbMap.put("addresses.locality", ContactsContract.CommonDataKinds.StructuredPostal.CITY); - dbMap.put("addresses.region", ContactsContract.CommonDataKinds.StructuredPostal.REGION); - dbMap.put("addresses.postalCode", ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE); - dbMap.put("addresses.country", ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY); - dbMap.put("ims", ContactsContract.CommonDataKinds.Im.DATA); - dbMap.put("ims.value", ContactsContract.CommonDataKinds.Im.DATA); - dbMap.put("organizations", ContactsContract.CommonDataKinds.Organization.COMPANY); - dbMap.put("organizations.name", ContactsContract.CommonDataKinds.Organization.COMPANY); - dbMap.put("organizations.department", ContactsContract.CommonDataKinds.Organization.DEPARTMENT); - dbMap.put("organizations.title", ContactsContract.CommonDataKinds.Organization.TITLE); - dbMap.put("birthday", ContactsContract.CommonDataKinds.Event.START_DATE); - dbMap.put("note", ContactsContract.CommonDataKinds.Note.NOTE); - dbMap.put("photos.value", ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE); - //dbMap.put("categories.value", null); - dbMap.put("urls", ContactsContract.CommonDataKinds.Website.URL); - dbMap.put("urls.value", ContactsContract.CommonDataKinds.Website.URL); + dbMap.put("id", ContactsContract.Data.CONTACT_ID); + dbMap.put("displayName", ContactsContract.Contacts.DISPLAY_NAME); + dbMap.put("name", ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + dbMap.put("name.formatted", ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + dbMap.put("name.familyName", ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME); + dbMap.put("name.givenName", ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME); + dbMap.put("name.middleName", ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME); + dbMap.put("name.honorificPrefix", ContactsContract.CommonDataKinds.StructuredName.PREFIX); + dbMap.put("name.honorificSuffix", ContactsContract.CommonDataKinds.StructuredName.SUFFIX); + dbMap.put("nickname", ContactsContract.CommonDataKinds.Nickname.NAME); + dbMap.put("phoneNumbers", ContactsContract.CommonDataKinds.Phone.NUMBER); + dbMap.put("phoneNumbers.value", ContactsContract.CommonDataKinds.Phone.NUMBER); + dbMap.put("emails", ContactsContract.CommonDataKinds.Email.DATA); + dbMap.put("emails.value", ContactsContract.CommonDataKinds.Email.DATA); + dbMap.put("addresses", ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + dbMap.put("addresses.formatted", ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + dbMap.put("addresses.streetAddress", ContactsContract.CommonDataKinds.StructuredPostal.STREET); + dbMap.put("addresses.locality", ContactsContract.CommonDataKinds.StructuredPostal.CITY); + dbMap.put("addresses.region", ContactsContract.CommonDataKinds.StructuredPostal.REGION); + dbMap.put("addresses.postalCode", ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE); + dbMap.put("addresses.country", ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY); + dbMap.put("ims", ContactsContract.CommonDataKinds.Im.DATA); + dbMap.put("ims.value", ContactsContract.CommonDataKinds.Im.DATA); + dbMap.put("organizations", ContactsContract.CommonDataKinds.Organization.COMPANY); + dbMap.put("organizations.name", ContactsContract.CommonDataKinds.Organization.COMPANY); + dbMap.put("organizations.department", ContactsContract.CommonDataKinds.Organization.DEPARTMENT); + dbMap.put("organizations.title", ContactsContract.CommonDataKinds.Organization.TITLE); + dbMap.put("birthday", ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE); + dbMap.put("note", ContactsContract.CommonDataKinds.Note.NOTE); + dbMap.put("photos.value", ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE); + //dbMap.put("categories.value", null); + dbMap.put("urls", ContactsContract.CommonDataKinds.Website.URL); + dbMap.put("urls.value", ContactsContract.CommonDataKinds.Website.URL); } /** * Create an contact accessor. */ - public ContactAccessorSdk5(WebView view, Context context) { - mApp = context; - mView = view; - } + public ContactAccessorSdk5(WebView view, CordovaInterface context) { + mApp = context; + mView = view; + } - /** - * This method takes the fields required and search options in order to produce an - * array of contacts that matches the criteria provided. - * @param fields an array of items to be used as search criteria - * @param options that can be applied to contact searching - * @return an array of contacts - */ - @Override - public JSONArray search(JSONArray fields, JSONObject options) { - // Get the find options - String searchTerm = ""; - int limit = Integer.MAX_VALUE; - boolean multiple = true; + /** + * This method takes the fields required and search options in order to produce an + * array of contacts that matches the criteria provided. + * @param fields an array of items to be used as search criteria + * @param options that can be applied to contact searching + * @return an array of contacts + */ + @Override + public JSONArray search(JSONArray fields, JSONObject options) { + // Get the find options + String searchTerm = ""; + int limit = Integer.MAX_VALUE; + boolean multiple = true; - if (options != null) { - searchTerm = options.optString("filter"); - if (searchTerm.length()==0) { - searchTerm = "%"; - } - else { - searchTerm = "%" + searchTerm + "%"; - } - try { - multiple = options.getBoolean("multiple"); - if (!multiple) { - limit = 1; + if (options != null) { + searchTerm = options.optString("filter"); + if (searchTerm.length() == 0) { + searchTerm = "%"; + } + else { + searchTerm = "%" + searchTerm + "%"; + } + try { + multiple = options.getBoolean("multiple"); + if (!multiple) { + limit = 1; + } + } catch (JSONException e) { + // Multiple was not specified so we assume the default is true. + } + } + else { + searchTerm = "%"; } - } catch (JSONException e) { - // Multiple was not specified so we assume the default is true. - } - } - else { - searchTerm = "%"; - } - //Log.d(LOG_TAG, "Search Term = " + searchTerm); - //Log.d(LOG_TAG, "Field Length = " + fields.length()); - //Log.d(LOG_TAG, "Fields = " + fields.toString()); + //Log.d(LOG_TAG, "Search Term = " + searchTerm); + //Log.d(LOG_TAG, "Field Length = " + fields.length()); + //Log.d(LOG_TAG, "Fields = " + fields.toString()); - // Loop through the fields the user provided to see what data should be returned. - HashMap populate = buildPopulationSet(fields); + // Loop through the fields the user provided to see what data should be returned. + HashMap populate = buildPopulationSet(fields); - // Build the ugly where clause and where arguments for one big query. - WhereOptions whereOptions = buildWhereClause(fields, searchTerm); + // Build the ugly where clause and where arguments for one big query. + WhereOptions whereOptions = buildWhereClause(fields, searchTerm); - // Get all the id's where the search term matches the fields passed in. - Cursor idCursor = mApp.getContentResolver().query(ContactsContract.Data.CONTENT_URI, - new String[] { ContactsContract.Data.CONTACT_ID }, - whereOptions.getWhere(), - whereOptions.getWhereArgs(), - ContactsContract.Data.CONTACT_ID + " ASC"); + // Get all the id's where the search term matches the fields passed in. + Cursor idCursor = mApp.getActivity().getContentResolver().query(ContactsContract.Data.CONTENT_URI, + new String[] { ContactsContract.Data.CONTACT_ID }, + whereOptions.getWhere(), + whereOptions.getWhereArgs(), + ContactsContract.Data.CONTACT_ID + " ASC"); - // Create a set of unique ids - //Log.d(LOG_TAG, "ID cursor query returns = " + idCursor.getCount()); - Set contactIds = new HashSet(); - while (idCursor.moveToNext()) { - contactIds.add(idCursor.getString(idCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID))); - } - idCursor.close(); + // Create a set of unique ids + //Log.d(LOG_TAG, "ID cursor query returns = " + idCursor.getCount()); + Set contactIds = new HashSet(); + while (idCursor.moveToNext()) { + contactIds.add(idCursor.getString(idCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID))); + } + idCursor.close(); - // Build a query that only looks at ids - WhereOptions idOptions = buildIdClause(contactIds, searchTerm); + // Build a query that only looks at ids + WhereOptions idOptions = buildIdClause(contactIds, searchTerm); - // Do the id query - Cursor c = mApp.getContentResolver().query(ContactsContract.Data.CONTENT_URI, - null, - idOptions.getWhere(), - idOptions.getWhereArgs(), - ContactsContract.Data.CONTACT_ID + " ASC"); - - JSONArray contacts = populateContactArray(limit, populate, c); - return contacts; - } - - /** - * A special search that finds one contact by id - * - * @param id contact to find by id - * @return a JSONObject representing the contact - * @throws JSONException - */ - public JSONObject getContactById(String id) throws JSONException { // Do the id query - Cursor c = mApp.getContentResolver().query(ContactsContract.Data.CONTENT_URI, + Cursor c = mApp.getActivity().getContentResolver().query(ContactsContract.Data.CONTENT_URI, + null, + idOptions.getWhere(), + idOptions.getWhereArgs(), + ContactsContract.Data.CONTACT_ID + " ASC"); + + JSONArray contacts = populateContactArray(limit, populate, c); + return contacts; + } + + /** + * A special search that finds one contact by id + * + * @param id contact to find by id + * @return a JSONObject representing the contact + * @throws JSONException + */ + public JSONObject getContactById(String id) throws JSONException { + // Do the id query + Cursor c = mApp.getActivity().getContentResolver().query(ContactsContract.Data.CONTENT_URI, null, ContactsContract.Data.CONTACT_ID + " = ? ", new String[] { id }, @@ -221,7 +220,7 @@ public class ContactAccessorSdk5 extends ContactAccessor { JSONArray fields = new JSONArray(); fields.put("*"); - HashMap populate = buildPopulationSet(fields); + HashMap populate = buildPopulationSet(fields); JSONArray contacts = populateContactArray(1, populate, c); @@ -230,199 +229,198 @@ public class ContactAccessorSdk5 extends ContactAccessor { } else { return null; } - } + } - /** - * Creates an array of contacts from the cursor you pass in - * - * @param limit max number of contacts for the array - * @param populate whether or not you should populate a certain value - * @param c the cursor - * @return a JSONArray of contacts - */ + /** + * Creates an array of contacts from the cursor you pass in + * + * @param limit max number of contacts for the array + * @param populate whether or not you should populate a certain value + * @param c the cursor + * @return a JSONArray of contacts + */ private JSONArray populateContactArray(int limit, HashMap populate, Cursor c) { - String contactId = ""; - String rawId = ""; - String oldContactId = ""; - boolean newContact = true; - String mimetype = ""; + String contactId = ""; + String rawId = ""; + String oldContactId = ""; + boolean newContact = true; + String mimetype = ""; - JSONArray contacts = new JSONArray(); - JSONObject contact = new JSONObject(); - JSONArray organizations = new JSONArray(); - JSONArray addresses = new JSONArray(); - JSONArray phones = new JSONArray(); - JSONArray emails = new JSONArray(); - JSONArray ims = new JSONArray(); - JSONArray websites = new JSONArray(); - JSONArray photos = new JSONArray(); + JSONArray contacts = new JSONArray(); + JSONObject contact = new JSONObject(); + JSONArray organizations = new JSONArray(); + JSONArray addresses = new JSONArray(); + JSONArray phones = new JSONArray(); + JSONArray emails = new JSONArray(); + JSONArray ims = new JSONArray(); + JSONArray websites = new JSONArray(); + JSONArray photos = new JSONArray(); - if (c.getCount() > 0) { - while (c.moveToNext() && (contacts.length() <= (limit-1))) { - try { - contactId = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID)); - rawId = c.getString(c.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID)); + if (c.getCount() > 0) { + while (c.moveToNext() && (contacts.length() <= (limit - 1))) { + try { + contactId = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID)); + rawId = c.getString(c.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID)); - // If we are in the first row set the oldContactId - if (c.getPosition() == 0) { - oldContactId = contactId; - } + // If we are in the first row set the oldContactId + if (c.getPosition() == 0) { + oldContactId = contactId; + } - // When the contact ID changes we need to push the Contact object - // to the array of contacts and create new objects. - if (!oldContactId.equals(contactId)) { - // Populate the Contact object with it's arrays - // and push the contact into the contacts array - contacts.put(populateContact(contact, organizations, addresses, phones, - emails, ims, websites, photos)); + // When the contact ID changes we need to push the Contact object + // to the array of contacts and create new objects. + if (!oldContactId.equals(contactId)) { + // Populate the Contact object with it's arrays + // and push the contact into the contacts array + contacts.put(populateContact(contact, organizations, addresses, phones, + emails, ims, websites, photos)); - // Clean up the objects - contact = new JSONObject(); - organizations = new JSONArray(); - addresses = new JSONArray(); - phones = new JSONArray(); - emails = new JSONArray(); - ims = new JSONArray(); - websites = new JSONArray(); - photos = new JSONArray(); + // Clean up the objects + contact = new JSONObject(); + organizations = new JSONArray(); + addresses = new JSONArray(); + phones = new JSONArray(); + emails = new JSONArray(); + ims = new JSONArray(); + websites = new JSONArray(); + photos = new JSONArray(); - // Set newContact to true as we are starting to populate a new contact - newContact = true; - } + // Set newContact to true as we are starting to populate a new contact + newContact = true; + } - // When we detect a new contact set the ID and display name. - // These fields are available in every row in the result set returned. - if (newContact) { - newContact = false; - contact.put("id", contactId); - contact.put("rawId", rawId); - } + // When we detect a new contact set the ID and display name. + // These fields are available in every row in the result set returned. + if (newContact) { + newContact = false; + contact.put("id", contactId); + contact.put("rawId", rawId); + } - // Grab the mimetype of the current row as it will be used in a lot of comparisons - mimetype = c.getString(c.getColumnIndex(ContactsContract.Data.MIMETYPE)); + // Grab the mimetype of the current row as it will be used in a lot of comparisons + mimetype = c.getString(c.getColumnIndex(ContactsContract.Data.MIMETYPE)); if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) { contact.put("displayName", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME))); } if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) - && isRequired("name",populate)) { + && isRequired("name", populate)) { contact.put("name", nameQuery(c)); } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) - && isRequired("phoneNumbers",populate)) { - phones.put(phoneQuery(c)); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) - && isRequired("emails",populate)) { - emails.put(emailQuery(c)); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) - && isRequired("addresses",populate)) { - addresses.put(addressQuery(c)); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) - && isRequired("organizations",populate)) { - organizations.put(organizationQuery(c)); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) - && isRequired("ims",populate)) { - ims.put(imQuery(c)); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) - && isRequired("note",populate)) { - contact.put("note",c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE))); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) - && isRequired("nickname",populate)) { - contact.put("nickname",c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME))); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) - && isRequired("urls",populate)) { - websites.put(websiteQuery(c)); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)) { - if (ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)) - && isRequired("birthday",populate)) { - contact.put("birthday", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE))); + else if (mimetype.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + && isRequired("phoneNumbers", populate)) { + phones.put(phoneQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + && isRequired("emails", populate)) { + emails.put(emailQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + && isRequired("addresses", populate)) { + addresses.put(addressQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + && isRequired("organizations", populate)) { + organizations.put(organizationQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) + && isRequired("ims", populate)) { + ims.put(imQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) + && isRequired("note", populate)) { + contact.put("note", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE))); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) + && isRequired("nickname", populate)) { + contact.put("nickname", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME))); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) + && isRequired("urls", populate)) { + websites.put(websiteQuery(c)); + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)) { + if (ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)) + && isRequired("birthday", populate)) { + contact.put("birthday", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE))); + } + } + else if (mimetype.equals(ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) + && isRequired("photos", populate)) { + photos.put(photoQuery(c, contactId)); + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + + // Set the old contact ID + oldContactId = contactId; } - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) - && isRequired("photos",populate)) { - photos.put(photoQuery(c, contactId)); - } - } - catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(),e); - } - // Set the old contact ID - oldContactId = contactId; - } - - // Push the last contact into the contacts array - if (contacts.length() < limit) { - contacts.put(populateContact(contact, organizations, addresses, phones, - emails, ims, websites, photos)); - } - } - c.close(); + // Push the last contact into the contacts array + if (contacts.length() < limit) { + contacts.put(populateContact(contact, organizations, addresses, phones, + emails, ims, websites, photos)); + } + } + c.close(); return contacts; } - /** - * Builds a where clause all all the ids passed into the method - * @param contactIds a set of unique contact ids - * @param searchTerm what to search for - * @return an object containing the selection and selection args - */ - private WhereOptions buildIdClause(Set contactIds, String searchTerm) { - WhereOptions options = new WhereOptions(); + /** + * Builds a where clause all all the ids passed into the method + * @param contactIds a set of unique contact ids + * @param searchTerm what to search for + * @return an object containing the selection and selection args + */ + private WhereOptions buildIdClause(Set contactIds, String searchTerm) { + WhereOptions options = new WhereOptions(); - // If the user is searching for every contact then short circuit the method - // and return a shorter where clause to be searched. - if (searchTerm.equals("%")) { - options.setWhere("(" + ContactsContract.Data.CONTACT_ID + " LIKE ? )"); - options.setWhereArgs(new String[] {searchTerm}); - return options; + // If the user is searching for every contact then short circuit the method + // and return a shorter where clause to be searched. + if (searchTerm.equals("%")) { + options.setWhere("(" + ContactsContract.Data.CONTACT_ID + " LIKE ? )"); + options.setWhereArgs(new String[] { searchTerm }); + return options; + } + + // This clause means that there are specific ID's to be populated + Iterator it = contactIds.iterator(); + StringBuffer buffer = new StringBuffer("("); + + while (it.hasNext()) { + buffer.append("'" + it.next() + "'"); + if (it.hasNext()) { + buffer.append(","); + } + } + buffer.append(")"); + + options.setWhere(ContactsContract.Data.CONTACT_ID + " IN " + buffer.toString()); + options.setWhereArgs(null); + + return options; } - // This clause means that there are specific ID's to be populated - Iterator it = contactIds.iterator(); - StringBuffer buffer = new StringBuffer("("); - - while (it.hasNext()) { - buffer.append("'" + it.next() + "'"); - if (it.hasNext()) { - buffer.append(","); - } - } - buffer.append(")"); - - options.setWhere(ContactsContract.Data.CONTACT_ID + " IN " + buffer.toString()); - options.setWhereArgs(null); - - return options; - } - - /** - * Create a new contact using a JSONObject to hold all the data. - * @param contact - * @param organizations array of organizations - * @param addresses array of addresses - * @param phones array of phones - * @param emails array of emails - * @param ims array of instant messenger addresses - * @param websites array of websites - * @param photos - * @return - */ - private JSONObject populateContact(JSONObject contact, JSONArray organizations, - JSONArray addresses, JSONArray phones, JSONArray emails, - JSONArray ims, JSONArray websites, JSONArray photos) { - try { - // Only return the array if it has at least one entry + /** + * Create a new contact using a JSONObject to hold all the data. + * @param contact + * @param organizations array of organizations + * @param addresses array of addresses + * @param phones array of phones + * @param emails array of emails + * @param ims array of instant messenger addresses + * @param websites array of websites + * @param photos + * @return + */ + private JSONObject populateContact(JSONObject contact, JSONArray organizations, + JSONArray addresses, JSONArray phones, JSONArray emails, + JSONArray ims, JSONArray websites, JSONArray photos) { + try { + // Only return the array if it has at least one entry if (organizations.length() > 0) { contact.put("organizations", organizations); } @@ -444,13 +442,12 @@ public class ContactAccessorSdk5 extends ContactAccessor { if (photos.length() > 0) { contact.put("photos", photos); } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return contact; } - catch (JSONException e) { - Log.e(LOG_TAG,e.getMessage(),e); - } - return contact; - } - + /** * Take the search criteria passed into the method and create a SQL WHERE clause. * @param fields the properties to search against @@ -461,9 +458,9 @@ public class ContactAccessorSdk5 extends ContactAccessor { ArrayList where = new ArrayList(); ArrayList whereArgs = new ArrayList(); - + WhereOptions options = new WhereOptions(); - + /* * Special case where the user wants all fields returned */ @@ -471,10 +468,10 @@ public class ContactAccessorSdk5 extends ContactAccessor { // Get all contacts with all properties if ("%".equals(searchTerm)) { options.setWhere("(" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? )"); - options.setWhereArgs(new String[] {searchTerm}); + options.setWhereArgs(new String[] { searchTerm }); return options; } else { - // Get all contacts that match the filter but return all properties + // Get all contacts that match the filter but return all properties where.add("(" + dbMap.get("displayName") + " LIKE ? )"); whereArgs.add(searchTerm); where.add("(" + dbMap.get("name") + " LIKE ? AND " @@ -516,1298 +513,1278 @@ public class ContactAccessorSdk5 extends ContactAccessor { } } - /* - * Special case for when the user wants all the contacts but - */ - if ("%".equals(searchTerm)) { - options.setWhere("(" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? )"); - options.setWhereArgs(new String[] {searchTerm}); - return options; - } + /* + * Special case for when the user wants all the contacts but + */ + if ("%".equals(searchTerm)) { + options.setWhere("(" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? )"); + options.setWhereArgs(new String[] { searchTerm }); + return options; + } - String key; - try { - //Log.d(LOG_TAG, "How many fields do we have = " + fields.length()); - for (int i=0; i 1) { - for(Account a : accounts) { - if(a.type.contains("eas")&& a.name.matches(EMAIL_REGEXP)) /*Exchange ActiveSync*/ { - accountName = a.name; - accountType = a.type; - break; - } - } - if(accountName == null){ - for(Account a : accounts){ - if(a.type.contains("com.google") && a.name.matches(EMAIL_REGEXP)) /*Google sync provider*/ { - accountName = a.name; - accountType = a.type; - break; - } - } - } - if(accountName == null){ - for(Account a : accounts){ - if(a.name.matches(EMAIL_REGEXP)) /*Last resort, just look for an email address...*/ { - accountName = a.name; - accountType = a.type; - break; - } - } + organization.put("department", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.DEPARTMENT))); + organization.put("name", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.COMPANY))); + organization.put("title", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.TITLE))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); } + return organization; } - String id = getJsonString(contact, "id"); - // Create new contact - if (id == null) { - return createNewContact(contact, accountType, accountName); - } - // Modify existing contact - else { - return modifyContact(id, contact, accountType, accountName); - } - } - - /** - * Creates a new contact and stores it in the database - * - * @param id the raw contact id which is required for linking items to the contact - * @param contact the contact to be saved - * @param account the account to be saved under - */ - private String modifyContact(String id, JSONObject contact, String accountType, String accountName) { - // Get the RAW_CONTACT_ID which is needed to insert new values in an already existing contact. - // But not needed to update existing values. - int rawId = (new Integer(getJsonString(contact,"rawId"))).intValue(); - - // Create a list of attributes to add to the contact database - ArrayList ops = new ArrayList(); - - //Add contact type - ops.add(ContentProviderOperation.newUpdate(ContactsContract.RawContacts.CONTENT_URI) - .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType) - .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName) - .build()); - - // Modify name - JSONObject name; - try { - String displayName = getJsonString(contact, "displayName"); - name = contact.getJSONObject("name"); - if (displayName != null || name != null) { - ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) - .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + - ContactsContract.Data.MIMETYPE + "=?", - new String[]{id, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}); - - if (displayName != null) { - builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName); - } - - String familyName = getJsonString(name, "familyName"); - if (familyName != null) { - builder.withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, familyName); - } - String middleName = getJsonString(name, "middleName"); - if (middleName != null) { - builder.withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, middleName); - } - String givenName = getJsonString(name, "givenName"); - if (givenName != null) { - builder.withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, givenName); - } - String honorificPrefix = getJsonString(name, "honorificPrefix"); - if (honorificPrefix != null) { - builder.withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, honorificPrefix); - } - String honorificSuffix = getJsonString(name, "honorificSuffix"); - if (honorificSuffix != null) { - builder.withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, honorificSuffix); - } - - ops.add(builder.build()); - } - } catch (JSONException e1) { - Log.d(LOG_TAG, "Could not get name"); - } - - // Modify phone numbers - JSONArray phones = null; - try { - phones = contact.getJSONArray("phoneNumbers"); - if (phones != null) { - for (int i=0; i ops, - JSONObject website) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Website.DATA, getJsonString(website, "value")) - .withValue(ContactsContract.CommonDataKinds.Website.TYPE, getContactType(getJsonString(website, "type"))) - .build()); - } - - /** - * Add an im to a list of database actions to be performed - * - * @param ops the list of database actions - * @param im the item to be inserted - */ - private void insertIm(ArrayList ops, JSONObject im) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")) - .withValue(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type"))) - .build()); - } - - /** - * Add an organization to a list of database actions to be performed - * - * @param ops the list of database actions - * @param org the item to be inserted - */ - private void insertOrganization(ArrayList ops, - JSONObject org) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))) - .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")) - .withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")) - .withValue(ContactsContract.CommonDataKinds.Organization.TITLE, getJsonString(org, "title")) - .build()); - } - - /** - * Add an address to a list of database actions to be performed - * - * @param ops the list of database actions - * @param address the item to be inserted - */ - private void insertAddress(ArrayList ops, - JSONObject address) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))) - .withValue(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")) - .withValue(ContactsContract.CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")) - .withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")) - .withValue(ContactsContract.CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")) - .withValue(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")) - .withValue(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY, getJsonString(address, "country")) - .build()); - } - - /** - * Add an email to a list of database actions to be performed - * - * @param ops the list of database actions - * @param email the item to be inserted - */ - private void insertEmail(ArrayList ops, - JSONObject email) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value")) - .withValue(ContactsContract.CommonDataKinds.Email.TYPE, getPhoneType(getJsonString(email, "type"))) - .build()); - } - - /** - * Add a phone to a list of database actions to be performed - * - * @param ops the list of database actions - * @param phone the item to be inserted - */ - private void insertPhone(ArrayList ops, - JSONObject phone) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")) - .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, getPhoneType(getJsonString(phone, "type"))) - .build()); - } - - /** - * Add a phone to a list of database actions to be performed - * - * @param ops the list of database actions - * @param phone the item to be inserted - */ - private void insertPhoto(ArrayList ops, - JSONObject photo) { - byte[] bytes = getPhotoBytes(getJsonString(photo, "value")); - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, bytes) - .build()); - } - - /** - * Gets the raw bytes from the supplied filename - * - * @param filename the file to read the bytes from - * @return a byte array - * @throws IOException - */ - private byte[] getPhotoBytes(String filename) { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - try { - int bytesRead = 0; - long totalBytesRead = 0; - byte[] data = new byte[8192]; - InputStream in = getPathFromUri(filename); - - while ((bytesRead = in.read(data, 0, data.length)) != -1 && totalBytesRead <= MAX_PHOTO_SIZE) { - buffer.write(data, 0, bytesRead); - totalBytesRead += bytesRead; - } - - in.close(); - buffer.flush(); - } catch (FileNotFoundException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } catch (IOException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - return buffer.toByteArray(); - } - /** - * Get an input stream based on file path or uri content://, http://, file:// - * - * @param path - * @return an input stream - * @throws IOException + /** + * Create a ContactAddress JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactAddress */ - private InputStream getPathFromUri(String path) throws IOException { - if (path.startsWith("content:")) { - Uri uri = Uri.parse(path); - return mApp.getContentResolver().openInputStream(uri); - } - if (path.startsWith("http:") || path.startsWith("file:")) { - URL url = new URL(path); - return url.openStream(); - } - else { - return new FileInputStream(path); - } + private JSONObject addressQuery(Cursor cursor) { + JSONObject address = new JSONObject(); + try { + address.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal._ID))); + address.put("pref", false); // Android does not store pref attribute + address.put("type", getAddressType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.TYPE)))); + address.put("formatted", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS))); + address.put("streetAddress", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.STREET))); + address.put("locality", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.CITY))); + address.put("region", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.REGION))); + address.put("postalCode", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE))); + address.put("country", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return address; } - /** - * Creates a new contact and stores it in the database - * - * @param contact the contact to be saved - * @param account the account to be saved under - */ - private String createNewContact(JSONObject contact, String accountType, String accountName) { - // Create a list of attributes to add to the contact database - ArrayList ops = new ArrayList(); + /** + * Create a ContactName JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactName + */ + private JSONObject nameQuery(Cursor cursor) { + JSONObject contactName = new JSONObject(); + try { + String familyName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)); + String givenName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)); + String middleName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)); + String honorificPrefix = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.PREFIX)); + String honorificSuffix = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.SUFFIX)); - //Add contact type - ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) - .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType) - .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName) - .build()); + // Create the formatted name + StringBuffer formatted = new StringBuffer(""); + if (honorificPrefix != null) { + formatted.append(honorificPrefix + " "); + } + if (givenName != null) { + formatted.append(givenName + " "); + } + if (middleName != null) { + formatted.append(middleName + " "); + } + if (familyName != null) { + formatted.append(familyName + " "); + } + if (honorificSuffix != null) { + formatted.append(honorificSuffix + " "); + } - // Add name - try { - JSONObject name = contact.optJSONObject("name"); - String displayName = contact.getString("displayName"); - if (displayName != null || name != null) { + contactName.put("familyName", familyName); + contactName.put("givenName", givenName); + contactName.put("middleName", middleName); + contactName.put("honorificPrefix", honorificPrefix); + contactName.put("honorificSuffix", honorificSuffix); + contactName.put("formatted", formatted); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return contactName; + } + + /** + * Create a ContactField JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactField + */ + private JSONObject phoneQuery(Cursor cursor) { + JSONObject phoneNumber = new JSONObject(); + try { + phoneNumber.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID))); + phoneNumber.put("pref", false); // Android does not store pref attribute + phoneNumber.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))); + phoneNumber.put("type", getPhoneType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } catch (Exception excp) { + Log.e(LOG_TAG, excp.getMessage(), excp); + } + return phoneNumber; + } + + /** + * Create a ContactField JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactField + */ + private JSONObject emailQuery(Cursor cursor) { + JSONObject email = new JSONObject(); + try { + email.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email._ID))); + email.put("pref", false); // Android does not store pref attribute + email.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA))); + email.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.TYPE)))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return email; + } + + /** + * Create a ContactField JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactField + */ + private JSONObject imQuery(Cursor cursor) { + JSONObject im = new JSONObject(); + try { + im.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im._ID))); + im.put("pref", false); // Android does not store pref attribute + im.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + im.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.TYPE)))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return im; + } + + /** + * Create a ContactField JSONObject + * @param cursor the current database row + * @return a JSONObject representing a ContactField + */ + private JSONObject websiteQuery(Cursor cursor) { + JSONObject website = new JSONObject(); + try { + website.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Website._ID))); + website.put("pref", false); // Android does not store pref attribute + website.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Website.URL))); + website.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Website.TYPE)))); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return website; + } + + /** + * Create a ContactField JSONObject + * @param contactId + * @return a JSONObject representing a ContactField + */ + private JSONObject photoQuery(Cursor cursor, String contactId) { + JSONObject photo = new JSONObject(); + try { + photo.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo._ID))); + photo.put("pref", false); + photo.put("type", "url"); + Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, (new Long(contactId))); + Uri photoUri = Uri.withAppendedPath(person, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); + photo.put("value", photoUri.toString()); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return photo; + } + + @Override + /** + * This method will save a contact object into the devices contacts database. + * + * @param contact the contact to be saved. + * @returns the id if the contact is successfully saved, null otherwise. + */ + public String save(JSONObject contact) { + AccountManager mgr = AccountManager.get(mApp.getActivity()); + Account[] accounts = mgr.getAccounts(); + String accountName = null; + String accountType = null; + + if (accounts.length == 1) { + accountName = accounts[0].name; + accountType = accounts[0].type; + } + else if (accounts.length > 1) { + for (Account a : accounts) { + if (a.type.contains("eas") && a.name.matches(EMAIL_REGEXP)) /*Exchange ActiveSync*/{ + accountName = a.name; + accountType = a.type; + break; + } + } + if (accountName == null) { + for (Account a : accounts) { + if (a.type.contains("com.google") && a.name.matches(EMAIL_REGEXP)) /*Google sync provider*/{ + accountName = a.name; + accountType = a.type; + break; + } + } + } + if (accountName == null) { + for (Account a : accounts) { + if (a.name.matches(EMAIL_REGEXP)) /*Last resort, just look for an email address...*/{ + accountName = a.name; + accountType = a.type; + break; + } + } + } + } + + String id = getJsonString(contact, "id"); + // Create new contact + if (id == null) { + return createNewContact(contact, accountType, accountName); + } + // Modify existing contact + else { + return modifyContact(id, contact, accountType, accountName); + } + } + + /** + * Creates a new contact and stores it in the database + * + * @param id the raw contact id which is required for linking items to the contact + * @param contact the contact to be saved + * @param account the account to be saved under + */ + private String modifyContact(String id, JSONObject contact, String accountType, String accountName) { + // Get the RAW_CONTACT_ID which is needed to insert new values in an already existing contact. + // But not needed to update existing values. + int rawId = (new Integer(getJsonString(contact, "rawId"))).intValue(); + + // Create a list of attributes to add to the contact database + ArrayList ops = new ArrayList(); + + //Add contact type + ops.add(ContentProviderOperation.newUpdate(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName) + .build()); + + // Modify name + JSONObject name; + try { + String displayName = getJsonString(contact, "displayName"); + name = contact.getJSONObject("name"); + if (displayName != null || name != null) { + ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { id, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE }); + + if (displayName != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName); + } + + String familyName = getJsonString(name, "familyName"); + if (familyName != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, familyName); + } + String middleName = getJsonString(name, "middleName"); + if (middleName != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, middleName); + } + String givenName = getJsonString(name, "givenName"); + if (givenName != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, givenName); + } + String honorificPrefix = getJsonString(name, "honorificPrefix"); + if (honorificPrefix != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, honorificPrefix); + } + String honorificSuffix = getJsonString(name, "honorificSuffix"); + if (honorificSuffix != null) { + builder.withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, honorificSuffix); + } + + ops.add(builder.build()); + } + } catch (JSONException e1) { + Log.d(LOG_TAG, "Could not get name"); + } + + // Modify phone numbers + JSONArray phones = null; + try { + phones = contact.getJSONArray("phoneNumbers"); + if (phones != null) { + for (int i = 0; i < phones.length(); i++) { + JSONObject phone = (JSONObject) phones.get(i); + String phoneId = getJsonString(phone, "id"); + // This is a new phone so do a DB insert + if (phoneId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")); + contentValues.put(ContactsContract.CommonDataKinds.Phone.TYPE, getPhoneType(getJsonString(phone, "type"))); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing phone so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.CommonDataKinds.Phone._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { phoneId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")) + .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, getPhoneType(getJsonString(phone, "type"))) + .build()); + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get phone numbers"); + } + + // Modify emails + JSONArray emails = null; + try { + emails = contact.getJSONArray("emails"); + if (emails != null) { + for (int i = 0; i < emails.length(); i++) { + JSONObject email = (JSONObject) emails.get(i); + String emailId = getJsonString(email, "id"); + // This is a new email so do a DB insert + if (emailId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value")); + contentValues.put(ContactsContract.CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type"))); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing email so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.CommonDataKinds.Email._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { emailId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value")) + .withValue(ContactsContract.CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type"))) + .build()); + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get emails"); + } + + // Modify addresses + JSONArray addresses = null; + try { + addresses = contact.getJSONArray("addresses"); + if (addresses != null) { + for (int i = 0; i < addresses.length(); i++) { + JSONObject address = (JSONObject) addresses.get(i); + String addressId = getJsonString(address, "id"); + // This is a new address so do a DB insert + if (addressId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")); + contentValues.put(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY, getJsonString(address, "country")); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing address so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.CommonDataKinds.StructuredPostal._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { addressId, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY, getJsonString(address, "country")) + .build()); + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get addresses"); + } + + // Modify organizations + JSONArray organizations = null; + try { + organizations = contact.getJSONArray("organizations"); + if (organizations != null) { + for (int i = 0; i < organizations.length(); i++) { + JSONObject org = (JSONObject) organizations.get(i); + String orgId = getJsonString(org, "id"); + // This is a new organization so do a DB insert + if (orgId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))); + contentValues.put(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")); + contentValues.put(ContactsContract.CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")); + contentValues.put(ContactsContract.CommonDataKinds.Organization.TITLE, getJsonString(org, "title")); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing organization so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.CommonDataKinds.Organization._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { orgId, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))) + .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")) + .withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")) + .withValue(ContactsContract.CommonDataKinds.Organization.TITLE, getJsonString(org, "title")) + .build()); + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get organizations"); + } + + // Modify IMs + JSONArray ims = null; + try { + ims = contact.getJSONArray("ims"); + if (ims != null) { + for (int i = 0; i < ims.length(); i++) { + JSONObject im = (JSONObject) ims.get(i); + String imId = getJsonString(im, "id"); + // This is a new IM so do a DB insert + if (imId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")); + contentValues.put(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type"))); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing IM so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.CommonDataKinds.Im._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { imId, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")) + .withValue(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type"))) + .build()); + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get emails"); + } + + // Modify note + String note = getJsonString(contact, "note"); + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { id, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Note.NOTE, note) + .build()); + + // Modify nickname + String nickname = getJsonString(contact, "nickname"); + if (nickname != null) { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { id, ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Nickname.NAME, nickname) + .build()); + } + + // Modify urls + JSONArray websites = null; + try { + websites = contact.getJSONArray("websites"); + if (websites != null) { + for (int i = 0; i < websites.length(); i++) { + JSONObject website = (JSONObject) websites.get(i); + String websiteId = getJsonString(website, "id"); + // This is a new website so do a DB insert + if (websiteId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.CommonDataKinds.Website.DATA, getJsonString(website, "value")); + contentValues.put(ContactsContract.CommonDataKinds.Website.TYPE, getContactType(getJsonString(website, "type"))); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing website so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.CommonDataKinds.Website._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { websiteId, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.CommonDataKinds.Website.DATA, getJsonString(website, "value")) + .withValue(ContactsContract.CommonDataKinds.Website.TYPE, getContactType(getJsonString(website, "type"))) + .build()); + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get websites"); + } + + // Modify birthday + String birthday = getJsonString(contact, "birthday"); + if (birthday != null) { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=? AND " + + ContactsContract.CommonDataKinds.Event.TYPE + "=?", + new String[] { id, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, new String("" + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) }) + .withValue(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) + .withValue(ContactsContract.CommonDataKinds.Event.START_DATE, birthday) + .build()); + } + + // Modify photos + JSONArray photos = null; + try { + photos = contact.getJSONArray("photos"); + if (photos != null) { + for (int i = 0; i < photos.length(); i++) { + JSONObject photo = (JSONObject) photos.get(i); + String photoId = getJsonString(photo, "id"); + byte[] bytes = getPhotoBytes(getJsonString(photo, "value")); + // This is a new photo so do a DB insert + if (photoId == null) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE); + contentValues.put(ContactsContract.Data.IS_SUPER_PRIMARY, 1); + contentValues.put(ContactsContract.CommonDataKinds.Photo.PHOTO, bytes); + + ops.add(ContentProviderOperation.newInsert( + ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); + } + // This is an existing photo so do a DB update + else { + ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI) + .withSelection(ContactsContract.CommonDataKinds.Photo._ID + "=? AND " + + ContactsContract.Data.MIMETYPE + "=?", + new String[] { photoId, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE }) + .withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1) + .withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, bytes) + .build()); + } + } + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get photos"); + } + + boolean retVal = true; + + //Modify contact + try { + mApp.getActivity().getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + } catch (RemoteException e) { + Log.e(LOG_TAG, e.getMessage(), e); + Log.e(LOG_TAG, Log.getStackTraceString(e), e); + retVal = false; + } catch (OperationApplicationException e) { + Log.e(LOG_TAG, e.getMessage(), e); + Log.e(LOG_TAG, Log.getStackTraceString(e), e); + retVal = false; + } + + // if the save was a succes return the contact ID + if (retVal) { + return id; + } else { + return null; + } + } + + /** + * Add a website to a list of database actions to be performed + * + * @param ops the list of database actions + * @param website the item to be inserted + */ + private void insertWebsite(ArrayList ops, + JSONObject website) { ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName) - .withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, getJsonString(name, "familyName")) - .withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, getJsonString(name, "middleName")) - .withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, getJsonString(name, "givenName")) - .withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, getJsonString(name, "honorificPrefix")) - .withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, getJsonString(name, "honorificSuffix")) - .build()); - } - } - catch (JSONException e) { - Log.d(LOG_TAG, "Could not get name object"); + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Website.DATA, getJsonString(website, "value")) + .withValue(ContactsContract.CommonDataKinds.Website.TYPE, getContactType(getJsonString(website, "type"))) + .build()); } - //Add phone numbers - JSONArray phones = null; - try { - phones = contact.getJSONArray("phoneNumbers"); - if (phones != null) { - for (int i=0; i ops, JSONObject im) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")) + .withValue(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type"))) + .build()); + } + + /** + * Add an organization to a list of database actions to be performed + * + * @param ops the list of database actions + * @param org the item to be inserted + */ + private void insertOrganization(ArrayList ops, + JSONObject org) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Organization.TYPE, getOrgType(getJsonString(org, "type"))) + .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, getJsonString(org, "department")) + .withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, getJsonString(org, "name")) + .withValue(ContactsContract.CommonDataKinds.Organization.TITLE, getJsonString(org, "title")) + .build()); + } + + /** + * Add an address to a list of database actions to be performed + * + * @param ops the list of database actions + * @param address the item to be inserted + */ + private void insertAddress(ArrayList ops, + JSONObject address) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, getAddressType(getJsonString(address, "type"))) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, getJsonString(address, "formatted")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.STREET, getJsonString(address, "streetAddress")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, getJsonString(address, "locality")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.REGION, getJsonString(address, "region")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, getJsonString(address, "postalCode")) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY, getJsonString(address, "country")) + .build()); + } + + /** + * Add an email to a list of database actions to be performed + * + * @param ops the list of database actions + * @param email the item to be inserted + */ + private void insertEmail(ArrayList ops, + JSONObject email) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value")) + .withValue(ContactsContract.CommonDataKinds.Email.TYPE, getPhoneType(getJsonString(email, "type"))) + .build()); + } + + /** + * Add a phone to a list of database actions to be performed + * + * @param ops the list of database actions + * @param phone the item to be inserted + */ + private void insertPhone(ArrayList ops, + JSONObject phone) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, getJsonString(phone, "value")) + .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, getPhoneType(getJsonString(phone, "type"))) + .build()); + } + + /** + * Add a phone to a list of database actions to be performed + * + * @param ops the list of database actions + * @param phone the item to be inserted + */ + private void insertPhoto(ArrayList ops, + JSONObject photo) { + byte[] bytes = getPhotoBytes(getJsonString(photo, "value")); + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, bytes) + .build()); + } + + /** + * Gets the raw bytes from the supplied filename + * + * @param filename the file to read the bytes from + * @return a byte array + * @throws IOException + */ + private byte[] getPhotoBytes(String filename) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try { + int bytesRead = 0; + long totalBytesRead = 0; + byte[] data = new byte[8192]; + InputStream in = getPathFromUri(filename); + + while ((bytesRead = in.read(data, 0, data.length)) != -1 && totalBytesRead <= MAX_PHOTO_SIZE) { + buffer.write(data, 0, bytesRead); + totalBytesRead += bytesRead; + } + + in.close(); + buffer.flush(); + } catch (FileNotFoundException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } catch (IOException e) { + Log.e(LOG_TAG, e.getMessage(), e); } - } - } - catch (JSONException e) { - Log.d(LOG_TAG, "Could not get phone numbers"); + return buffer.toByteArray(); } - // Add emails - JSONArray emails = null; - try { - emails = contact.getJSONArray("emails"); - if (emails != null) { - for (int i=0; i ops = new ArrayList(); + + //Add contact type + ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName) + .build()); + + // Add name + try { + JSONObject name = contact.optJSONObject("name"); + String displayName = contact.getString("displayName"); + if (displayName != null || name != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName) + .withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, getJsonString(name, "familyName")) + .withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, getJsonString(name, "middleName")) + .withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, getJsonString(name, "givenName")) + .withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, getJsonString(name, "honorificPrefix")) + .withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, getJsonString(name, "honorificSuffix")) + .build()); + } + } catch (JSONException e) { + Log.d(LOG_TAG, "Could not get name object"); } - } - } - catch (JSONException e) { - Log.d(LOG_TAG, "Could not get emails"); - } - // Add note - String note = getJsonString(contact, "note"); - if (note != null) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Note.NOTE, note) - .build()); - } - - // Add nickname - String nickname = getJsonString(contact, "nickname"); - if (nickname != null) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Nickname.NAME, nickname) - .build()); - } - - // Add urls - JSONArray websites = null; - try { - websites = contact.getJSONArray("websites"); - if (websites != null) { - for (int i=0; i= 0) { newId = cpResults[0].uri.getLastPathSegment(); } - } catch (RemoteException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } catch (OperationApplicationException e) { - Log.e(LOG_TAG, e.getMessage(), e); + } catch (RemoteException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } catch (OperationApplicationException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + return newId; } - return newId; - } - @Override - /** - * This method will remove a Contact from the database based on ID. - * @param id the unique ID of the contact to remove - */ - public boolean remove(String id) { + @Override + /** + * This method will remove a Contact from the database based on ID. + * @param id the unique ID of the contact to remove + */ + public boolean remove(String id) { int result = 0; - Cursor cursor = mApp.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, - null, - ContactsContract.Contacts._ID + " = ?", - new String[] {id}, null); - if(cursor.getCount() == 1) { + Cursor cursor = mApp.getActivity().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, + null, + ContactsContract.Contacts._ID + " = ?", + new String[] { id }, null); + if (cursor.getCount() == 1) { cursor.moveToFirst(); String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)); Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey); - result = mApp.getContentResolver().delete(uri, null, null); + result = mApp.getActivity().getContentResolver().delete(uri, null, null); } else { - Log.d(LOG_TAG, "Could not find contact with ID"); + Log.d(LOG_TAG, "Could not find contact with ID"); } - return (result > 0) ? true : false; - } - -/************************************************************************** - * - * All methods below this comment are used to convert from JavaScript - * text types to Android integer types and vice versa. - * - *************************************************************************/ - - /** - * Converts a string from the W3C Contact API to it's Android int value. - * @param string - * @return Android int value - */ - private int getPhoneType(String string) { - int type = ContactsContract.CommonDataKinds.Phone.TYPE_OTHER; - if (string != null) { - if ("home".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_HOME; - } - else if ("mobile".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE; - } - else if ("work".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_WORK; - } - else if ("work fax".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK; - } - else if ("home fax".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME; - } - else if ("fax".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK; - } - else if ("pager".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_PAGER; - } - else if ("other".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_OTHER; - } - else if ("car".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_CAR; - } - else if ("company main".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN; - } - else if ("isdn".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_ISDN; - } - else if ("main".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_MAIN; - } - else if ("other fax".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX; - } - else if ("radio".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_RADIO; - } - else if ("telex".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_TELEX; - } - else if ("work mobile".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE; - } - else if ("work pager".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER; - } - else if ("assistant".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT; - } - else if ("mms".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_MMS; - } - else if ("callback".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK; - } - else if ("tty ttd".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD; - } - else if ("custom".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM; - } + return (result > 0) ? true : false; } - return type; - } - /** - * getPhoneType converts an Android phone type into a string - * @param type - * @return phone type as string. - */ - private String getPhoneType(int type) { - String stringType; - switch (type) { - case ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM: - stringType = "custom"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME: - stringType = "home fax"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK: - stringType = "work fax"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_HOME: - stringType = "home"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE: - stringType = "mobile"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_PAGER: - stringType = "pager"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_WORK: - stringType = "work"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK: - stringType = "callback"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_CAR: - stringType = "car"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN: - stringType = "company main"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX: - stringType = "other fax"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_RADIO: - stringType = "radio"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_TELEX: - stringType = "telex"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD: - stringType = "tty tdd"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE: - stringType = "work mobile"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER: - stringType = "work pager"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT: - stringType = "assistant"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_MMS: - stringType = "mms"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_ISDN: - stringType = "isdn"; - break; - case ContactsContract.CommonDataKinds.Phone.TYPE_OTHER: - default: - stringType = "other"; - break; + /************************************************************************** + * + * All methods below this comment are used to convert from JavaScript + * text types to Android integer types and vice versa. + * + *************************************************************************/ + + /** + * Converts a string from the W3C Contact API to it's Android int value. + * @param string + * @return Android int value + */ + private int getPhoneType(String string) { + int type = ContactsContract.CommonDataKinds.Phone.TYPE_OTHER; + if (string != null) { + if ("home".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_HOME; + } + else if ("mobile".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE; + } + else if ("work".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_WORK; + } + else if ("work fax".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK; + } + else if ("home fax".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME; + } + else if ("fax".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK; + } + else if ("pager".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_PAGER; + } + else if ("other".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_OTHER; + } + else if ("car".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_CAR; + } + else if ("company main".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN; + } + else if ("isdn".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_ISDN; + } + else if ("main".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_MAIN; + } + else if ("other fax".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX; + } + else if ("radio".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_RADIO; + } + else if ("telex".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_TELEX; + } + else if ("work mobile".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE; + } + else if ("work pager".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER; + } + else if ("assistant".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT; + } + else if ("mms".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_MMS; + } + else if ("callback".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK; + } + else if ("tty ttd".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD; + } + else if ("custom".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM; + } + } + return type; + } + + /** + * getPhoneType converts an Android phone type into a string + * @param type + * @return phone type as string. + */ + private String getPhoneType(int type) { + String stringType; + switch (type) { + case ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM: + stringType = "custom"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME: + stringType = "home fax"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK: + stringType = "work fax"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_HOME: + stringType = "home"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE: + stringType = "mobile"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_PAGER: + stringType = "pager"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_WORK: + stringType = "work"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK: + stringType = "callback"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_CAR: + stringType = "car"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN: + stringType = "company main"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX: + stringType = "other fax"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_RADIO: + stringType = "radio"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_TELEX: + stringType = "telex"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD: + stringType = "tty tdd"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE: + stringType = "work mobile"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER: + stringType = "work pager"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT: + stringType = "assistant"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_MMS: + stringType = "mms"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_ISDN: + stringType = "isdn"; + break; + case ContactsContract.CommonDataKinds.Phone.TYPE_OTHER: + default: + stringType = "other"; + break; + } + return stringType; } - return stringType; - } /** * Converts a string from the W3C Contact API to it's Android int value. @@ -1816,7 +1793,7 @@ public class ContactAccessorSdk5 extends ContactAccessor { */ private int getContactType(String string) { int type = ContactsContract.CommonDataKinds.Email.TYPE_OTHER; - if (string!=null) { + if (string != null) { if ("home".equals(string.toLowerCase())) { return ContactsContract.CommonDataKinds.Email.TYPE_HOME; } @@ -1838,28 +1815,28 @@ public class ContactAccessorSdk5 extends ContactAccessor { /** * getPhoneType converts an Android phone type into a string - * @param type + * @param type * @return phone type as string. */ private String getContactType(int type) { String stringType; switch (type) { - case ContactsContract.CommonDataKinds.Email.TYPE_CUSTOM: - stringType = "custom"; - break; - case ContactsContract.CommonDataKinds.Email.TYPE_HOME: - stringType = "home"; - break; - case ContactsContract.CommonDataKinds.Email.TYPE_WORK: - stringType = "work"; - break; - case ContactsContract.CommonDataKinds.Email.TYPE_MOBILE: - stringType = "mobile"; - break; - case ContactsContract.CommonDataKinds.Email.TYPE_OTHER: - default: - stringType = "other"; - break; + case ContactsContract.CommonDataKinds.Email.TYPE_CUSTOM: + stringType = "custom"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_HOME: + stringType = "home"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_WORK: + stringType = "work"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_MOBILE: + stringType = "mobile"; + break; + case ContactsContract.CommonDataKinds.Email.TYPE_OTHER: + default: + stringType = "other"; + break; } return stringType; } @@ -1871,7 +1848,7 @@ public class ContactAccessorSdk5 extends ContactAccessor { */ private int getOrgType(String string) { int type = ContactsContract.CommonDataKinds.Organization.TYPE_OTHER; - if (string!=null) { + if (string != null) { if ("work".equals(string.toLowerCase())) { return ContactsContract.CommonDataKinds.Organization.TYPE_WORK; } @@ -1887,22 +1864,22 @@ public class ContactAccessorSdk5 extends ContactAccessor { /** * getPhoneType converts an Android phone type into a string - * @param type + * @param type * @return phone type as string. */ private String getOrgType(int type) { String stringType; switch (type) { - case ContactsContract.CommonDataKinds.Organization.TYPE_CUSTOM: - stringType = "custom"; - break; - case ContactsContract.CommonDataKinds.Organization.TYPE_WORK: - stringType = "work"; - break; - case ContactsContract.CommonDataKinds.Organization.TYPE_OTHER: - default: - stringType = "other"; - break; + case ContactsContract.CommonDataKinds.Organization.TYPE_CUSTOM: + stringType = "custom"; + break; + case ContactsContract.CommonDataKinds.Organization.TYPE_WORK: + stringType = "work"; + break; + case ContactsContract.CommonDataKinds.Organization.TYPE_OTHER: + default: + stringType = "other"; + break; } return stringType; } @@ -1914,7 +1891,7 @@ public class ContactAccessorSdk5 extends ContactAccessor { */ private int getAddressType(String string) { int type = ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER; - if (string!=null) { + if (string != null) { if ("work".equals(string.toLowerCase())) { return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK; } @@ -1930,23 +1907,24 @@ public class ContactAccessorSdk5 extends ContactAccessor { /** * getPhoneType converts an Android phone type into a string - * @param type + * @param type * @return phone type as string. */ private String getAddressType(int type) { String stringType; switch (type) { - case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME: - stringType = "home"; - break; - case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK: - stringType = "work"; - break; - case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER: - default: - stringType = "other"; - break; + case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME: + stringType = "home"; + break; + case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK: + stringType = "work"; + break; + case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER: + default: + stringType = "other"; + break; } return stringType; } } + diff --git a/framework/src/org/apache/cordova/ContactManager.java b/framework/src/org/apache/cordova/ContactManager.java index 55a9739f..b07fba0a 100755 --- a/framework/src/org/apache/cordova/ContactManager.java +++ b/framework/src/org/apache/cordova/ContactManager.java @@ -69,7 +69,7 @@ public class ContactManager extends Plugin { * older phones. */ if (this.contactAccessor == null) { - this.contactAccessor = new ContactAccessorSdk5(this.webView, this.ctx.getContext()); + this.contactAccessor = new ContactAccessorSdk5(this.webView, this.ctx); } try { diff --git a/framework/src/org/apache/cordova/CordovaChromeClient.java b/framework/src/org/apache/cordova/CordovaChromeClient.java index 1125e629..a23aac4e 100755 --- a/framework/src/org/apache/cordova/CordovaChromeClient.java +++ b/framework/src/org/apache/cordova/CordovaChromeClient.java @@ -17,16 +17,18 @@ under the License. */ package org.apache.cordova; + +import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.LOG; import org.json.JSONArray; import org.json.JSONException; - +//import android.app.Activity; import android.app.AlertDialog; -import android.content.Context; +//import android.content.Context; import android.content.DialogInterface; import android.view.KeyEvent; -import android.view.View; +//import android.view.View; import android.webkit.ConsoleMessage; import android.webkit.JsPromptResult; import android.webkit.JsResult; @@ -41,18 +43,38 @@ import android.widget.EditText; */ public class CordovaChromeClient extends WebChromeClient { - private String TAG = "CordovaLog"; private long MAX_QUOTA = 100 * 1024 * 1024; - private DroidGap ctx; + private CordovaInterface ctx; + private CordovaWebView appView; /** * Constructor. * * @param ctx */ - public CordovaChromeClient(Context ctx) { - this.ctx = (DroidGap) ctx; + public CordovaChromeClient(CordovaInterface ctx) { + this.ctx = ctx; + } + + /** + * Constructor. + * + * @param ctx + * @param app + */ + public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) { + this.ctx = ctx; + this.appView = app; + } + + /** + * Constructor. + * + * @param view + */ + public void setWebView(CordovaWebView view) { + this.appView = view; } /** @@ -65,35 +87,35 @@ public class CordovaChromeClient extends WebChromeClient { */ @Override public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { - AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); + AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx.getActivity()); dlg.setMessage(message); dlg.setTitle("Alert"); //Don't let alerts break the back button dlg.setCancelable(true); 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.setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - result.confirm(); - } - }); + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + result.confirm(); + } + }); dlg.setOnKeyListener(new DialogInterface.OnKeyListener() { //DO NOTHING public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { - if(keyCode == KeyEvent.KEYCODE_BACK) + if (keyCode == KeyEvent.KEYCODE_BACK) { result.confirm(); return false; } else return true; - } - }); + } + }); dlg.create(); dlg.show(); return true; @@ -109,40 +131,40 @@ public class CordovaChromeClient extends WebChromeClient { */ @Override public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) { - AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); + AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx.getActivity()); dlg.setMessage(message); dlg.setTitle("Confirm"); dlg.setCancelable(true); 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.setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - result.cancel(); + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + result.cancel(); } }); dlg.setOnKeyListener(new DialogInterface.OnKeyListener() { //DO NOTHING public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { - if(keyCode == KeyEvent.KEYCODE_BACK) + if (keyCode == KeyEvent.KEYCODE_BACK) { result.cancel(); return false; } else return true; - } - }); + } + }); dlg.create(); dlg.show(); return true; @@ -168,11 +190,11 @@ public class CordovaChromeClient extends WebChromeClient { // 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.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0 || ctx.isUrlWhiteListed(url)) { + if (url.startsWith("file://") || url.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) { reqOk = true; } - // Calling PluginManager.exec() to call a native service using + // 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; @@ -182,72 +204,66 @@ public class CordovaChromeClient extends WebChromeClient { String action = array.getString(1); String callbackId = array.getString(2); boolean async = array.getBoolean(3); - String r = ctx.pluginManager.exec(service, action, callbackId, message, async); + String r = this.appView.pluginManager.exec(service, action, callbackId, message, async); result.confirm(r); } catch (JSONException e) { e.printStackTrace(); } } - // Polling for JavaScript messages + // Polling for JavaScript messages else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { - String r = ctx.callbackServer.getJavascript(); + String r = this.appView.callbackServer.getJavascript(); result.confirm(r); } + // Do NO-OP so older code doesn't display dialog + else if (defaultValue != null && defaultValue.equals("gap_init:")) { + result.confirm("OK"); + } + // Calling into CallbackServer else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { String r = ""; if (message.equals("usePolling")) { - r = ""+ ctx.callbackServer.usePolling(); + r = "" + this.appView.callbackServer.usePolling(); } else if (message.equals("restartServer")) { - ctx.callbackServer.restartServer(); + this.appView.callbackServer.restartServer(); } else if (message.equals("getPort")) { - r = Integer.toString(ctx.callbackServer.getPort()); + r = Integer.toString(this.appView.callbackServer.getPort()); } else if (message.equals("getToken")) { - r = ctx.callbackServer.getToken(); + r = this.appView.callbackServer.getToken(); } result.confirm(r); } - // Cordova JS has initialized, so show webview - // (This solves white flash seen when rendering HTML) - else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) { - if (ctx.splashscreen != 0) { - ctx.root.setBackgroundResource(0); - } - ctx.appView.setVisibility(View.VISIBLE); - ctx.spinnerStop(); - result.confirm("OK"); - } - // Show dialog else { final JsPromptResult res = result; - AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); + AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx.getActivity()); dlg.setMessage(message); - final EditText input = new EditText(this.ctx); + final EditText input = new EditText(this.ctx.getActivity()); 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); - } - }); + 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(); - } - }); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + res.cancel(); + } + }); dlg.create(); dlg.show(); } @@ -270,7 +286,7 @@ public class CordovaChromeClient extends WebChromeClient { { LOG.d(TAG, "DroidGap: onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota); - if( estimatedSize < MAX_QUOTA) + if (estimatedSize < MAX_QUOTA) { //increase for 1Mb long newQuota = estimatedSize; @@ -286,6 +302,7 @@ public class CordovaChromeClient extends WebChromeClient { } // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html + @SuppressWarnings("deprecation") @Override public void onConsoleMessage(String message, int lineNumber, String sourceID) { @@ -296,7 +313,7 @@ public class CordovaChromeClient extends WebChromeClient { @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { - if(consoleMessage.message() != null) + if (consoleMessage.message() != null) LOG.d(TAG, consoleMessage.message()); return super.onConsoleMessage(consoleMessage); } @@ -312,6 +329,4 @@ public class CordovaChromeClient extends WebChromeClient { super.onGeolocationPermissionsShowPrompt(origin, callback); callback.invoke(origin, true, false); } - - } diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java new file mode 100755 index 00000000..308d222f --- /dev/null +++ b/framework/src/org/apache/cordova/CordovaWebView.java @@ -0,0 +1,663 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.cordova.api.CordovaInterface; +import org.apache.cordova.api.LOG; +import org.apache.cordova.api.PluginManager; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.Intent; +import android.content.res.XmlResourceParser; +import android.net.Uri; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.WindowManager; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebSettings.LayoutAlgorithm; + +public class CordovaWebView extends WebView { + + public static final String TAG = "CordovaWebView"; + + /** The whitelist **/ + private ArrayList whiteList = new ArrayList(); + private HashMap whiteListCache = new HashMap(); + public PluginManager pluginManager; + public CallbackServer callbackServer; + + /** Actvities and other important classes **/ + private CordovaInterface mCtx; + CordovaWebViewClient viewClient; + @SuppressWarnings("unused") + private CordovaChromeClient chromeClient; + + //This is for the polyfil history + private String url; + String baseUrl; + private Stack urls = new Stack(); + + boolean useBrowserHistory = false; + + // Flag to track that a loadUrl timeout occurred + int loadUrlTimeout = 0; + + /** + * Constructor. + * + * @param context + */ + public CordovaWebView(Context context) { + super(context); + if (CordovaInterface.class.isInstance(context)) + { + this.mCtx = (CordovaInterface) context; + } + else + { + Log.d(TAG, "Your activity must implement CordovaInterface to work"); + } + this.loadConfiguration(); + this.setup(); + } + + /** + * Constructor. + * + * @param context + * @param attrs + */ + public CordovaWebView(Context context, AttributeSet attrs) { + super(context, attrs); + if (CordovaInterface.class.isInstance(context)) + { + this.mCtx = (CordovaInterface) context; + } + else + { + Log.d(TAG, "Your activity must implement CordovaInterface to work"); + } + this.setWebChromeClient(new CordovaChromeClient(this.mCtx, this)); + this.setWebViewClient(new CordovaWebViewClient(this.mCtx, this)); + this.loadConfiguration(); + this.setup(); + } + + /** + * Constructor. + * + * @param context + * @param attrs + * @param defStyle + * + */ + public CordovaWebView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + if (CordovaInterface.class.isInstance(context)) + { + this.mCtx = (CordovaInterface) context; + } + else + { + Log.d(TAG, "Your activity must implement CordovaInterface to work"); + } + this.setWebChromeClient(new CordovaChromeClient(this.mCtx, this)); + this.setWebViewClient(new CordovaWebViewClient(this.mCtx, this)); + this.loadConfiguration(); + this.setup(); + } + + /** + * Constructor. + * + * @param context + * @param attrs + * @param defStyle + * @param privateBrowsing + */ + public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) { + super(context, attrs, defStyle, privateBrowsing); + if (CordovaInterface.class.isInstance(context)) + { + this.mCtx = (CordovaInterface) context; + } + else + { + Log.d(TAG, "Your activity must implement CordovaInterface to work"); + } + this.setWebChromeClient(new CordovaChromeClient(this.mCtx)); + this.setWebViewClient(new CordovaWebViewClient(this.mCtx)); + this.loadConfiguration(); + this.setup(); + } + + /** + * Initialize webview. + */ + @SuppressWarnings("deprecation") + private void setup() { + + this.setInitialScale(0); + this.setVerticalScrollBarEnabled(false); + this.requestFocusFromTouch(); + + // Enable JavaScript + WebSettings settings = this.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setJavaScriptCanOpenWindowsAutomatically(true); + settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL); + + //Set the nav dump for HTC + settings.setNavDump(true); + + // Enable database + settings.setDatabaseEnabled(true); + String databasePath = this.mCtx.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); + settings.setDatabasePath(databasePath); + + // Enable DOM storage + settings.setDomStorageEnabled(true); + + // Enable built-in geolocation + settings.setGeolocationEnabled(true); + + //Start up the plugin manager + try { + this.pluginManager = new PluginManager(this, this.mCtx); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Set the WebViewClient. + * + * @param client + */ + public void setWebViewClient(CordovaWebViewClient client) { + this.viewClient = client; + super.setWebViewClient(client); + } + + /** + * Set the WebChromeClient. + * + * @param client + */ + public void setWebChromeClient(CordovaChromeClient client) { + this.chromeClient = client; + super.setWebChromeClient(client); + } + + /** + * Add entry to approved list of URLs (whitelist) + * + * @param origin URL regular expression to allow + * @param subdomains T=include all subdomains under origin + */ + public void addWhiteListEntry(String origin, boolean subdomains) { + try { + // Unlimited access to network resources + if (origin.compareTo("*") == 0) { + LOG.d(TAG, "Unlimited access to network resources"); + this.whiteList.add(Pattern.compile(".*")); + } else { // specific access + // check if subdomains should be included + // TODO: we should not add more domains if * has already been added + if (subdomains) { + // XXX making it stupid friendly for people who forget to include protocol/SSL + if (origin.startsWith("http")) { + this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?"))); + } else { + this.whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin)); + } + LOG.d(TAG, "Origin to allow with subdomains: %s", origin); + } else { + // XXX making it stupid friendly for people who forget to include protocol/SSL + if (origin.startsWith("http")) { + this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://"))); + } else { + this.whiteList.add(Pattern.compile("^https?://" + origin)); + } + LOG.d(TAG, "Origin to allow: %s", origin); + } + } + } catch (Exception e) { + LOG.d(TAG, "Failed to add origin %s", origin); + } + } + + /** + * Determine if URL is in approved list of URLs to load. + * + * @param url + * @return + */ + public boolean isUrlWhiteListed(String url) { + + // Check to see if we have matched url previously + if (this.whiteListCache.get(url) != null) { + return true; + } + + // Look for match in white list + Iterator pit = this.whiteList.iterator(); + while (pit.hasNext()) { + Pattern p = pit.next(); + Matcher m = p.matcher(url); + + // If match found, then cache it to speed up subsequent comparisons + if (m.find()) { + this.whiteListCache.put(url, true); + return true; + } + } + return false; + } + + /** + * Load the url into the webview. + * + * @param url + */ + @Override + public void loadUrl(String url) { + if (url.equals("about:blank") || url.startsWith("javascript:")) { + this.loadUrlNow(url); + } + else { + + String initUrl = this.getProperty("url", null); + + // If first page of app, then set URL to load to be the one passed in + if (initUrl == null || (this.urls.size() > 0)) { + this.loadUrlIntoView(url); + } + // Otherwise use the URL specified in the activity's extras bundle + else { + this.loadUrlIntoView(initUrl); + } + } + } + + /** + * 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, int time) { + String initUrl = this.getProperty("url", null); + + // If first page of app, then set URL to load to be the one passed in + if (initUrl == null || (this.urls.size() > 0)) { + this.loadUrlIntoView(url, time); + } + // Otherwise use the URL specified in the activity's extras bundle + else { + this.loadUrlIntoView(initUrl); + } + } + + /** + * Load the url into the webview. + * + * @param url + */ + public void loadUrlIntoView(final String url) { + LOG.d(TAG, ">>> 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 + "/"; + } + + this.pluginManager.init(); + + if (!this.useBrowserHistory) { + this.urls.push(url); + } + } + + // Create a timeout timer for loadUrl + final CordovaWebView me = this; + final int currentLoadUrlTimeout = me.loadUrlTimeout; + final int loadUrlTimeoutValue = Integer.parseInt(this.getProperty("loadUrlTimeoutValue", "20000")); + + // Timeout error method + final Runnable loadError = new Runnable() { + public void run() { + me.stopLoading(); + LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!"); + if (viewClient != null) { + viewClient.onReceivedError(me, -6, "The connection to the server was unsuccessful.", url); + } + } + }; + + // Timeout timer method + final Runnable timeoutCheck = new Runnable() { + public void run() { + try { + synchronized (this) { + wait(loadUrlTimeoutValue); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // If timeout, then stop loading and handle error + if (me.loadUrlTimeout == currentLoadUrlTimeout) { + me.mCtx.getActivity().runOnUiThread(loadError); + } + } + }; + + // Load url + this.mCtx.getActivity().runOnUiThread(new Runnable() { + public void run() { + Thread thread = new Thread(timeoutCheck); + thread.start(); + me.loadUrlNow(url); + } + }); + } + + /** + * Load URL in webview. + * + * @param url + */ + private void loadUrlNow(String url) { + LOG.d(TAG, ">>> loadUrlNow()"); + super.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 loadUrlIntoView(final String url, final int time) { + + // If not first page of app, then load immediately + // Add support for browser history if we use it. + if ((url.startsWith("javascript:")) || this.urls.size() > 0 || this.canGoBack()) { + } + + // If first page, then show splashscreen + else { + + LOG.d(TAG, "DroidGap.loadUrl(%s, %d)", url, time); + + // Send message to show splashscreen now if desired + this.postMessage("splashscreen", "show"); + } + + // Load url + this.loadUrlIntoView(url); + } + + /** + * Send JavaScript statement back to JavaScript. + * (This is a convenience method) + * + * @param message + */ + public void sendJavascript(String statement) { + if (this.callbackServer != null) { + this.callbackServer.sendJavascript(statement); + } + } + + /** + * Send a message to all plugins. + * + * @param id The message id + * @param data The message data + */ + public void postMessage(String id, Object data) { + if (this.pluginManager != null) { + this.pluginManager.postMessage(id, data); + } + } + + /** + * Returns the top url on the stack without removing it from + * the stack. + */ + public String peekAtUrlStack() { + if (this.urls.size() > 0) { + return this.urls.peek(); + } + return ""; + } + + /** + * Add a url to the stack + * + * @param url + */ + public void pushUrl(String url) { + this.urls.push(url); + } + + /** + * Go to previous page in history. (We manage our own history) + * + * @return true if we went back, false if we are already at top + */ + public boolean backHistory() { + + // Check webview first to see if there is a history + // This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior) + if (super.canGoBack()) { + super.goBack(); + return true; + } + + // If our managed history has prev url + if (this.urls.size() > 1) { + this.urls.pop(); // Pop current url + String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl() + this.loadUrl(url); + return true; + } + + return false; + } + + /** + * Return true if there is a history item. + * + * @return + */ + public boolean canGoBack() { + if (super.canGoBack()) { + return true; + } + if (this.urls.size() > 1) { + return true; + } + return false; + } + + /** + * Load the specified URL in the Cordova webview or a new browser instance. + * + * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded. + * + * @param url The url to load. + * @param openExternal Load url in browser instead of Cordova webview. + * @param clearHistory Clear the history stack, so new page becomes top of history + * @param params DroidGap parameters for new app + */ + public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap params) { + LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory); + + // If clearing history + if (clearHistory) { + this.clearHistory(); + } + + // If loading into our webview + if (!openExternal) { + + // Make sure url is in whitelist + if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || isUrlWhiteListed(url)) { + // TODO: What about params? + + // Clear out current url from history, since it will be replacing it + if (clearHistory) { + this.urls.clear(); + } + + // Load new URL + this.loadUrl(url); + } + // Load in default viewer if not + else { + LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")"); + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + mCtx.getActivity().startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + LOG.e(TAG, "Error loading url " + url, e); + } + } + } + + // Load in default view intent + else { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + mCtx.getActivity().startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + LOG.e(TAG, "Error loading url " + url, e); + } + } + } + + /** + * Load Cordova configuration from res/xml/cordova.xml. + * Approved list of URLs that can be loaded into DroidGap + * + * Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR) + * + */ + private void loadConfiguration() { + int id = getResources().getIdentifier("cordova", "xml", this.mCtx.getActivity().getPackageName()); + if (id == 0) { + LOG.i("CordovaLog", "cordova.xml missing. Ignoring..."); + return; + } + XmlResourceParser xml = getResources().getXml(id); + int eventType = -1; + while (eventType != XmlResourceParser.END_DOCUMENT) { + if (eventType == XmlResourceParser.START_TAG) { + String strNode = xml.getName(); + if (strNode.equals("access")) { + String origin = xml.getAttributeValue(null, "origin"); + String subdomains = xml.getAttributeValue(null, "subdomains"); + if (origin != null) { + this.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); + } + } + else if (strNode.equals("log")) { + String level = xml.getAttributeValue(null, "level"); + LOG.i("CordovaLog", "Found log level %s", level); + if (level != null) { + LOG.setLogLevel(level); + } + } + else if (strNode.equals("preference")) { + String name = xml.getAttributeValue(null, "name"); + String value = xml.getAttributeValue(null, "value"); + + LOG.i("CordovaLog", "Found preference for %s=%s", name, value); + + // Save preferences in Intent + this.mCtx.getActivity().getIntent().putExtra(name, value); + } + } + try { + eventType = xml.next(); + } catch (XmlPullParserException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Init preferences + if ("true".equals(this.getProperty("useBrowserHistory", "true"))) { + this.useBrowserHistory = true; + } + else { + this.useBrowserHistory = false; + } + + if ("true".equals(this.getProperty("fullscreen", "false"))) { + this.mCtx.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + this.mCtx.getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + /** + * Get string property for activity. + * + * @param name + * @param defaultValue + * @return + */ + public String getProperty(String name, String defaultValue) { + Bundle bundle = this.mCtx.getActivity().getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Object p = bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.toString(); + } +} diff --git a/framework/src/org/apache/cordova/CordovaWebViewClient.java b/framework/src/org/apache/cordova/CordovaWebViewClient.java index e2bd344c..3cda1b11 100755 --- a/framework/src/org/apache/cordova/CordovaWebViewClient.java +++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java @@ -18,7 +18,12 @@ */ package org.apache.cordova; +import java.util.Hashtable; + +import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.LOG; +import org.json.JSONException; +import org.json.JSONObject; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -39,18 +44,42 @@ import android.webkit.WebViewClient; public class CordovaWebViewClient extends WebViewClient { private static final String TAG = "Cordova"; - DroidGap ctx; + CordovaInterface ctx; + CordovaWebView appView; private boolean doClearHistory = false; + /** The authorization tokens. */ + private Hashtable authenticationTokens = new Hashtable(); + /** * Constructor. * * @param ctx */ - public CordovaWebViewClient(DroidGap ctx) { + public CordovaWebViewClient(CordovaInterface ctx) { this.ctx = ctx; } + /** + * Constructor. + * + * @param ctx + * @param view + */ + public CordovaWebViewClient(CordovaInterface ctx, CordovaWebView view) { + this.ctx = ctx; + this.appView = view; + } + + /** + * Constructor. + * + * @param view + */ + public void setWebView(CordovaWebView view) { + this.appView = view; + } + /** * Give the host application a chance to take over the control when a new url * is about to be loaded in the current WebView. @@ -63,7 +92,7 @@ public class CordovaWebViewClient extends WebViewClient { public boolean shouldOverrideUrlLoading(WebView view, String url) { // First give any plugins the chance to handle the url themselves - if ((this.ctx.pluginManager != null) && this.ctx.pluginManager.onOverrideUrlLoading(url)) { + if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) { } // If dialing phone (tel:5551212) @@ -71,9 +100,9 @@ public class CordovaWebViewClient extends WebViewClient { try { Intent intent = new Intent(Intent.ACTION_DIAL); intent.setData(Uri.parse(url)); - ctx.startActivity(intent); + this.ctx.getActivity().startActivity(intent); } catch (android.content.ActivityNotFoundException e) { - LOG.e(TAG, "Error dialing "+url+": "+ e.toString()); + LOG.e(TAG, "Error dialing " + url + ": " + e.toString()); } } @@ -82,9 +111,9 @@ public class CordovaWebViewClient extends WebViewClient { try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); - ctx.startActivity(intent); + this.ctx.getActivity().startActivity(intent); } catch (android.content.ActivityNotFoundException e) { - LOG.e(TAG, "Error showing map "+url+": "+ e.toString()); + LOG.e(TAG, "Error showing map " + url + ": " + e.toString()); } } @@ -93,9 +122,9 @@ public class CordovaWebViewClient extends WebViewClient { try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); - ctx.startActivity(intent); + this.ctx.getActivity().startActivity(intent); } catch (android.content.ActivityNotFoundException e) { - LOG.e(TAG, "Error sending email "+url+": "+ e.toString()); + LOG.e(TAG, "Error sending email " + url + ": " + e.toString()); } } @@ -122,12 +151,12 @@ public class CordovaWebViewClient extends WebViewClient { } } } - intent.setData(Uri.parse("sms:"+address)); + intent.setData(Uri.parse("sms:" + address)); intent.putExtra("address", address); intent.setType("vnd.android-dir/mms-sms"); - ctx.startActivity(intent); + this.ctx.getActivity().startActivity(intent); } catch (android.content.ActivityNotFoundException e) { - LOG.e(TAG, "Error sending sms "+url+":"+ e.toString()); + LOG.e(TAG, "Error sending sms " + url + ":" + e.toString()); } } @@ -136,8 +165,12 @@ public class CordovaWebViewClient extends WebViewClient { // If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity. // Our app continues to run. When BACK is pressed, our app is redisplayed. - if (url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0 || ctx.isUrlWhiteListed(url)) { - this.ctx.loadUrl(url); + if (url.startsWith("file://") || url.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) { + //This will fix iFrames + if (appView.useBrowserHistory) + return false; + else + this.appView.loadUrl(url); } // If not our application, let default viewer handle @@ -145,9 +178,9 @@ public class CordovaWebViewClient extends WebViewClient { try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); - ctx.startActivity(intent); + this.ctx.getActivity().startActivity(intent); } catch (android.content.ActivityNotFoundException e) { - LOG.e(TAG, "Error loading url "+url, e); + LOG.e(TAG, "Error loading url " + url, e); } } } @@ -159,44 +192,62 @@ public class CordovaWebViewClient extends WebViewClient { * The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination * * @param view - * the view * @param handler - * the handler * @param host - * the host * @param realm - * the realm */ @Override - public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, - String realm) { + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { - // get the authentication token - AuthenticationToken token = ctx.getAuthenticationToken(host,realm); - - if(token != null) { + // Get the authentication token + AuthenticationToken token = this.getAuthenticationToken(host, realm); + if (token != null) { handler.proceed(token.getUserName(), token.getPassword()); } } - + /** + * Notify the host application that a page has started loading. + * This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted + * one time for the main frame. This also means that onPageStarted will not be called when the contents of an + * embedded frame changes, i.e. clicking a link whose target is an iframe. + * + * @param view The webview initiating the callback. + * @param url The url of the page. + */ @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { // Clear history so history.back() doesn't do anything. // So we can reinit() native side CallbackServer & PluginManager. - view.clearHistory(); - this.doClearHistory = true; + if (!this.appView.useBrowserHistory) { + view.clearHistory(); + this.doClearHistory = true; + } + + // Create callback server and plugin manager + if (this.appView.callbackServer == null) { + this.appView.callbackServer = new CallbackServer(); + this.appView.callbackServer.init(url); + } + else { + this.appView.callbackServer.reinit(url); + } + + // Broadcast message that page has loaded + this.appView.postMessage("onPageStarted", url); } /** * Notify the host application that a page has finished loading. - * + * This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet. + * * @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); + LOG.d(TAG, "onPageFinished(" + url + ")"); /** * Because of a timing issue we need to clear this history in onPageFinished as well as @@ -210,29 +261,28 @@ public class CordovaWebViewClient extends WebViewClient { } // Clear timeout flag - this.ctx.loadUrlTimeout++; + this.appView.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")) { - ctx.appView.loadUrl("javascript:try{ cordova.require('cordova/channel').onNativeReady.fire();}catch(e){_nativeReady = true;}"); - this.ctx.postMessage("onNativeReady", null); + this.appView.loadUrl("javascript:try{ cordova.require('cordova/channel').onNativeReady.fire();}catch(e){_nativeReady = true;}"); + this.appView.postMessage("onNativeReady", null); } + // Broadcast message that page has loaded + this.appView.postMessage("onPageFinished", url); + // Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly - if (ctx.appView.getVisibility() == View.INVISIBLE) { + if (this.appView.getVisibility() == View.INVISIBLE) { Thread t = new Thread(new Runnable() { public void run() { try { Thread.sleep(2000); - ctx.runOnUiThread(new Runnable() { + ctx.getActivity().runOnUiThread(new Runnable() { public void run() { - if (ctx.splashscreen != 0) { - ctx.root.setBackgroundResource(0); - } - ctx.appView.setVisibility(View.VISIBLE); - ctx.spinnerStop(); + appView.postMessage("spinner", "stop"); } }); } catch (InterruptedException e) { @@ -242,13 +292,12 @@ public class CordovaWebViewClient extends WebViewClient { t.start(); } - // Shutdown if blank loaded if (url.equals("about:blank")) { - if (this.ctx.callbackServer != null) { - this.ctx.callbackServer.destroy(); + if (this.appView.callbackServer != null) { + this.appView.callbackServer.destroy(); } - this.ctx.endActivity(); + appView.postMessage("exit", null); } } @@ -263,22 +312,38 @@ public class CordovaWebViewClient extends WebViewClient { */ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - LOG.d(TAG, "DroidGap: GapViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl); + LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl); // Clear timeout flag - this.ctx.loadUrlTimeout++; - - // Stop "app loading" spinner if showing - this.ctx.spinnerStop(); + this.appView.loadUrlTimeout++; // Handle error - this.ctx.onReceivedError(errorCode, description, failingUrl); + JSONObject data = new JSONObject(); + try { + data.put("errorCode", errorCode); + data.put("description", description); + data.put("url", failingUrl); + } catch (JSONException e) { + e.printStackTrace(); + } + this.appView.postMessage("onReceivedError", data); } + /** + * Notify the host application that an SSL error occurred while loading a resource. + * The host application must call either handler.cancel() or handler.proceed(). + * Note that the decision may be retained for use in response to future SSL errors. + * The default behavior is to cancel the load. + * + * @param view The WebView that is initiating the callback. + * @param handler An SslErrorHandler object that will handle the user's response. + * @param error The SSL error object. + */ + @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - final String packageName = this.ctx.getPackageName(); - final PackageManager pm = this.ctx.getPackageManager(); + final String packageName = this.ctx.getActivity().getPackageName(); + final PackageManager pm = this.ctx.getActivity().getPackageManager(); ApplicationInfo appInfo; try { appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); @@ -296,14 +361,94 @@ public class CordovaWebViewClient extends WebViewClient { } } + /** + * Notify the host application to update its visited links database. + * + * @param view The WebView that is initiating the callback. + * @param url The url being visited. + * @param isReload True if this url is being reloaded. + */ @Override public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { /* * If you do a document.location.href the url does not get pushed on the stack * so we do a check here to see if the url should be pushed. */ - if (!this.ctx.peekAtUrlStack().equals(url)) { - this.ctx.pushUrl(url); + if (!this.appView.peekAtUrlStack().equals(url)) { + this.appView.pushUrl(url); } } + + /** + * Sets the authentication token. + * + * @param authenticationToken + * @param host + * @param realm + */ + public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) { + if (host == null) { + host = ""; + } + if (realm == null) { + realm = ""; + } + this.authenticationTokens.put(host.concat(realm), authenticationToken); + } + + /** + * Removes the authentication token. + * + * @param host + * @param realm + * + * @return the authentication token or null if did not exist + */ + public AuthenticationToken removeAuthenticationToken(String host, String realm) { + return this.authenticationTokens.remove(host.concat(realm)); + } + + /** + * Gets the authentication token. + * + * In order it tries: + * 1- host + realm + * 2- host + * 3- realm + * 4- no host, no realm + * + * @param host + * @param realm + * + * @return the authentication token + */ + public AuthenticationToken getAuthenticationToken(String host, String realm) { + AuthenticationToken token = null; + token = this.authenticationTokens.get(host.concat(realm)); + + if (token == null) { + // try with just the host + token = this.authenticationTokens.get(host); + + // Try the realm + if (token == null) { + token = this.authenticationTokens.get(realm); + } + + // if no host found, just query for default + if (token == null) { + token = this.authenticationTokens.get(""); + } + } + + return token; + } + + /** + * Clear all authentication tokens. + */ + public void clearAuthenticationTokens() { + this.authenticationTokens.clear(); + } + } diff --git a/framework/src/org/apache/cordova/Device.java b/framework/src/org/apache/cordova/Device.java index 2c2f1179..6fd151ea 100644 --- a/framework/src/org/apache/cordova/Device.java +++ b/framework/src/org/apache/cordova/Device.java @@ -110,7 +110,7 @@ public class Device extends Plugin { * Unregister receiver. */ public void onDestroy() { - this.ctx.unregisterReceiver(this.telephonyReceiver); + this.ctx.getActivity().unregisterReceiver(this.telephonyReceiver); } //-------------------------------------------------------------------------- @@ -123,9 +123,9 @@ public class Device extends Plugin { * DroidGap.onMessage("telephone", "ringing" | "offhook" | "idle") */ private void initTelephonyReceiver() { - IntentFilter intentFilter = new IntentFilter() ; + IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); - final CordovaInterface myctx = this.ctx; + //final CordovaInterface myctx = this.ctx; this.telephonyReceiver = new BroadcastReceiver() { @Override @@ -137,15 +137,15 @@ public class Device extends Plugin { String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE); if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) { LOG.i(TAG, "Telephone RINGING"); - myctx.postMessage("telephone", "ringing"); + webView.postMessage("telephone", "ringing"); } else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) { LOG.i(TAG, "Telephone OFFHOOK"); - myctx.postMessage("telephone", "offhook"); + webView.postMessage("telephone", "offhook"); } else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) { LOG.i(TAG, "Telephone IDLE"); - myctx.postMessage("telephone", "idle"); + webView.postMessage("telephone", "idle"); } } } @@ -153,7 +153,7 @@ public class Device extends Plugin { }; // Register the receiver - this.ctx.registerReceiver(this.telephonyReceiver, intentFilter); + this.ctx.getActivity().registerReceiver(this.telephonyReceiver, intentFilter); } /** @@ -171,7 +171,7 @@ public class Device extends Plugin { * @return */ public String getUuid() { - String uuid = Settings.Secure.getString(this.ctx.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); + String uuid = Settings.Secure.getString(this.ctx.getActivity().getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); return uuid; } @@ -205,15 +205,14 @@ public class Device extends Plugin { } public String getSDKVersion() { + @SuppressWarnings("deprecation") String sdkversion = android.os.Build.VERSION.SDK; return sdkversion; } - public String getTimeZoneID() { - TimeZone tz = TimeZone.getDefault(); - return(tz.getID()); + TimeZone tz = TimeZone.getDefault(); + return (tz.getID()); } } - diff --git a/framework/src/org/apache/cordova/DirectoryManager.java b/framework/src/org/apache/cordova/DirectoryManager.java index 4696d00c..292f4029 100644 --- a/framework/src/org/apache/cordova/DirectoryManager.java +++ b/framework/src/org/apache/cordova/DirectoryManager.java @@ -32,11 +32,11 @@ import android.os.StatFs; */ public class DirectoryManager { + @SuppressWarnings("unused") private static final String LOG_TAG = "DirectoryManager"; /** * Determine if a file or directory exists. - * * @param name The name of the file to check. * @return T=exists, F=not found */ @@ -50,7 +50,7 @@ public class DirectoryManager { status = newPath.exists(); } // If no SD card - else{ + else { status = false; } return status; @@ -58,7 +58,7 @@ public class DirectoryManager { /** * Get the free disk space - * + * * @return Size in KB or -1 if not available */ protected static long getFreeDiskSpace(boolean checkInternal) { @@ -82,7 +82,7 @@ public class DirectoryManager { /** * Given a path return the number of free KB - * + * * @param path to the file system * @return free space in KB */ @@ -90,12 +90,12 @@ public class DirectoryManager { StatFs stat = new StatFs(path); long blockSize = stat.getBlockSize(); long availableBlocks = stat.getAvailableBlocks(); - return availableBlocks*blockSize/1024; + return availableBlocks * blockSize / 1024; } /** * Determine if SD card exists. - * + * * @return T=exists, F=not found */ protected static boolean testSaveLocationExists() { @@ -127,7 +127,7 @@ public class DirectoryManager { newPath = new File(file2); } else { - newPath = new File(file1+"/"+file2); + newPath = new File(file1 + "/" + file2); } return newPath; } diff --git a/framework/src/org/apache/cordova/DroidGap.java b/framework/src/org/apache/cordova/DroidGap.java index ddd50a6e..cf55dde7 100755 --- a/framework/src/org/apache/cordova/DroidGap.java +++ b/framework/src/org/apache/cordova/DroidGap.java @@ -18,22 +18,13 @@ */ package org.apache.cordova; -import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.cordova.PreferenceNode; -import org.apache.cordova.PreferenceSet; import org.apache.cordova.api.IPlugin; import org.apache.cordova.api.LOG; import org.apache.cordova.api.CordovaInterface; -import org.apache.cordova.api.PluginManager; -import org.xmlpull.v1.XmlPullParserException; +import org.json.JSONException; +import org.json.JSONObject; import android.app.Activity; import android.app.AlertDialog; @@ -43,10 +34,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; -import android.content.res.XmlResourceParser; import android.graphics.Color; import android.media.AudioManager; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.view.Display; @@ -57,15 +46,9 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; -import android.webkit.WebSettings.LayoutAlgorithm; -import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.LinearLayout; - - /** * This class is the main Android activity that represents the Cordova * application. It should be extended by the user to load the specific @@ -151,25 +134,17 @@ public class DroidGap extends Activity implements CordovaInterface { public static String TAG = "DroidGap"; // The webview for our app - protected WebView appView; - protected WebViewClient webViewClient; - private ArrayList whiteList = new ArrayList(); - private HashMap whiteListCache = new HashMap(); + protected CordovaWebView appView; + protected CordovaWebViewClient webViewClient; protected LinearLayout root; public boolean bound = false; - public CallbackServer callbackServer; - protected PluginManager pluginManager; protected boolean cancelLoadUrl = false; protected ProgressDialog spinnerDialog = null; // The initial URL for our app // ie http://server/path/index.html#abc?query - private String url = null; - private Stack urls = new Stack(); - - // Url was specified from extras (activity was started programmatically) - private String initUrl = null; + //private String url = null; private static int ACTIVITY_STARTING = 0; private static int ACTIVITY_RUNNING = 1; @@ -185,16 +160,10 @@ public class DroidGap extends Activity implements CordovaInterface { protected IPlugin activityResultCallback = null; protected boolean activityResultKeepRunning; - // Flag indicates that a loadUrl timeout occurred - 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 authorization tokens. */ - private Hashtable authenticationTokens = new Hashtable(); - /* * The variables below are used to cache some of the activity properties. */ @@ -202,6 +171,7 @@ public class DroidGap extends Activity implements CordovaInterface { // 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; + protected int splashscreenTime = 0; // LoadUrl timeout value in msec (default of 20 sec) protected int loadUrlTimeoutValue = 20000; @@ -211,43 +181,36 @@ public class DroidGap extends Activity implements CordovaInterface { // when another application (activity) is started. protected boolean keepRunning = true; - // preferences read from cordova.xml - protected PreferenceSet preferences; + private boolean volumeupBound; + + private boolean volumedownBound; /** - * Sets the authentication token. - * - * @param authenticationToken - * the authentication token - * @param host - * the host - * @param realm - * the realm - */ + * Sets the authentication token. + * + * @param authenticationToken + * @param host + * @param realm + */ public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) { - - if(host == null) { - host = ""; + if (this.appView != null && this.appView.viewClient != null) { + this.appView.viewClient.setAuthenticationToken(authenticationToken, host, realm); } - - if(realm == null) { - realm = ""; - } - - authenticationTokens.put(host.concat(realm), authenticationToken); } /** * Removes the authentication token. * * @param host - * the host * @param realm - * the realm + * * @return the authentication token or null if did not exist */ public AuthenticationToken removeAuthenticationToken(String host, String realm) { - return authenticationTokens.remove(host.concat(realm)); + if (this.appView != null && this.appView.viewClient != null) { + return this.appView.viewClient.removeAuthenticationToken(host, realm); + } + return null; } /** @@ -260,68 +223,42 @@ public class DroidGap extends Activity implements CordovaInterface { * 4- no host, no realm * * @param host - * the host * @param realm - * the realm + * * @return the authentication token */ public AuthenticationToken getAuthenticationToken(String host, String realm) { - AuthenticationToken token = null; - - token = authenticationTokens.get(host.concat(realm)); - - if(token == null) { - // try with just the host - token = authenticationTokens.get(host); - - // Try the realm - if(token == null) { - token = authenticationTokens.get(realm); - } - - // if no host found, just query for default - if(token == null) { - token = authenticationTokens.get(""); - } + if (this.appView != null && this.appView.viewClient != null) { + return this.appView.viewClient.getAuthenticationToken(host, realm); } - - return token; + return null; } /** * Clear all authentication tokens. */ public void clearAuthenticationTokens() { - authenticationTokens.clear(); + if (this.appView != null && this.appView.viewClient != null) { + this.appView.viewClient.clearAuthenticationTokens(); + } } - /** * Called when the activity is first created. * * @param savedInstanceState */ + @SuppressWarnings("deprecation") @Override public void onCreate(Bundle savedInstanceState) { - preferences = new PreferenceSet(); - - // Load Cordova configuration: - // white list of allowed URLs - // debug setting - this.loadConfiguration(); + //preferences = new PreferenceSet(); LOG.d(TAG, "DroidGap.onCreate()"); super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_NO_TITLE); - - if (preferences.prefMatches("fullscreen","true")) { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - } else { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - } + 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(); @@ -331,26 +268,28 @@ public class DroidGap extends Activity implements CordovaInterface { 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)); + root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_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.initUrl = url; - } - } // Setup the hardware volume controls to handle volume control setVolumeControlStream(AudioManager.STREAM_MUSIC); } + /** + * Get the Android activity. + * + * @return + */ + public Activity getActivity() { + return this; + } + /** * Create and initialize web container with default web view objects. */ public void init() { - this.init(new WebView(DroidGap.this), new CordovaWebViewClient(this), new CordovaChromeClient(DroidGap.this)); + CordovaWebView webView = new CordovaWebView(DroidGap.this); + this.init(webView, new CordovaWebViewClient(this, webView), new CordovaChromeClient(this, webView)); } /** @@ -360,89 +299,30 @@ public class DroidGap extends Activity implements CordovaInterface { * @param webViewClient * @param webChromeClient */ - public void init(WebView webView, WebViewClient webViewClient, WebChromeClient webChromeClient) { + public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) { LOG.d(TAG, "DroidGap.init()"); // Set up web container - this.appView = webView; + this.appView = webView; this.appView.setId(100); + this.appView.setWebViewClient(webViewClient); + this.appView.setWebChromeClient(webChromeClient); + webViewClient.setWebView(this.appView); + webChromeClient.setWebView(this.appView); + this.appView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, 1.0F)); - this.appView.setWebChromeClient(webChromeClient); - this.setWebViewClient(this.appView, webViewClient); - - this.appView.setInitialScale(0); - this.appView.setVerticalScrollBarEnabled(false); - this.appView.requestFocusFromTouch(); - - // Enable JavaScript - WebSettings settings = this.appView.getSettings(); - settings.setJavaScriptEnabled(true); - settings.setJavaScriptCanOpenWindowsAutomatically(true); - settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL); - - //Set the nav dump for HTC - settings.setNavDump(true); - - // Enable database - settings.setDatabaseEnabled(true); - String databasePath = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); - settings.setDatabasePath(databasePath); - - // Enable DOM storage - settings.setDomStorageEnabled(true); - - // Enable built-in geolocation - settings.setGeolocationEnabled(true); - // Add web view but make it invisible while loading URL this.appView.setVisibility(View.INVISIBLE); - root.addView(this.appView); - setContentView(root); + this.root.addView(this.appView); + setContentView(this.root); // Clear cancel flag this.cancelLoadUrl = false; - - // Create plugin manager - this.pluginManager = new PluginManager(this.appView, this); - } - - /** - * Set the WebViewClient. - * - * @param appView - * @param client - */ - protected void setWebViewClient(WebView appView, WebViewClient client) { - this.webViewClient = client; - appView.setWebViewClient(client); - } - - /** - * Look at activity parameters and process them. - * This must be called from the main UI thread. - */ - private void handleActivityParameters() { - - // If backgroundColor - this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); - this.root.setBackgroundColor(this.backgroundColor); - - // If spashscreen - this.splashscreen = this.getIntegerProperty("splashscreen", 0); - - // If loadUrlTimeoutValue - int timeout = this.getIntegerProperty("loadUrlTimeoutValue", 0); - if (timeout > 0) { - this.loadUrlTimeoutValue = timeout; - } - - // If keepRunning - this.keepRunning = this.getBooleanProperty("keepRunning", true); } /** @@ -452,119 +332,55 @@ public class DroidGap extends Activity implements CordovaInterface { */ public void loadUrl(String url) { - // If first page of app, then set URL to load to be the one passed in - if (this.initUrl == null || (this.urls.size() > 0)) { - this.loadUrlIntoView(url); - } - // Otherwise use the URL specified in the activity's extras bundle - else { - this.loadUrlIntoView(this.initUrl); + // Init web view if not already done + if (this.appView == null) { + this.init(); } + + // Set backgroundColor + this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); + this.root.setBackgroundColor(this.backgroundColor); + + // If keepRunning + this.keepRunning = this.getBooleanProperty("keepRunning", true); + + // Then load the spinner + this.loadSpinner(); + + this.appView.loadUrl(url); } - /** - * Load the url into the webview. - * - * @param url + /* + * Load the spinner */ - private void loadUrlIntoView(final String url) { - if (!url.startsWith("javascript:")) { - LOG.d(TAG, "DroidGap.loadUrl(%s)", url); + void loadSpinner() { + + // If loadingDialog property, then show the App loading dialog for first page of app + String loading = null; + if ((this.appView == null) || !this.appView.canGoBack()) { + loading = this.getStringProperty("loadingDialog", null); } - - 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 + "/"; - } - } - if (!url.startsWith("javascript:")) { - LOG.d(TAG, "DroidGap: url=%s baseUrl=%s", url, baseUrl); + else { + loading = this.getStringProperty("loadingPageDialog", null); } + if (loading != null) { - // Load URL on UI thread - final DroidGap me = this; - this.runOnUiThread(new Runnable() { - public void run() { + String title = ""; + String message = "Loading Application..."; - // Init web view if not already done - if (me.appView == null) { - me.init(); - } - - // Handle activity parameters - me.handleActivityParameters(); - - // Track URLs loaded instead of using appView history - me.urls.push(url); - me.appView.clearHistory(); - - // Create callback server and plugin manager - if (me.callbackServer == null) { - me.callbackServer = new CallbackServer(); - me.callbackServer.init(url); + if (loading.length() > 0) { + int comma = loading.indexOf(','); + if (comma > 0) { + title = loading.substring(0, comma); + message = loading.substring(comma + 1); } else { - me.callbackServer.reinit(url); + title = ""; + message = loading; } - me.pluginManager.init(); - - // If loadingDialog property, then show the App loading dialog for first page of app - String loading = null; - if (me.urls.size() == 1) { - loading = me.getStringProperty("loadingDialog", null); - } - else { - loading = me.getStringProperty("loadingPageDialog", null); - } - if (loading != null) { - - 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); - } - - // 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(); - LOG.e(TAG, "DroidGap: TIMEOUT ERROR! - calling webViewClient"); - 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); } - }); + this.spinnerStart(title, message); + } } /** @@ -576,47 +392,20 @@ public class DroidGap extends Activity implements CordovaInterface { */ public void loadUrl(final String url, int time) { - // If first page of app, then set URL to load to be the one passed in - if (this.initUrl == null || (this.urls.size() > 0)) { - this.loadUrlIntoView(url, time); - } - // Otherwise use the URL specified in the activity's extras bundle - else { - this.loadUrlIntoView(this.initUrl); - } - } - - /** - * 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 - */ - private void loadUrlIntoView(final String url, final int time) { - - // Clear cancel flag - this.cancelLoadUrl = false; - - // If not first page of app, then load immediately - if (this.urls.size() > 0) { - this.loadUrlIntoView(url); + // Init web view if not already done + if (this.appView == null) { + this.init(); } - if (!url.startsWith("javascript:")) { - LOG.d(TAG, "DroidGap.loadUrl(%s, %d)", url, time); - } - - this.handleActivityParameters(); - if (this.splashscreen != 0) { - this.showSplashScreen(time); - } - this.loadUrlIntoView(url); + this.splashscreenTime = time; + this.appView.loadUrl(url, time); } /** * Cancel loadUrl before it has been loaded. */ + // TODO NO-OP + @Deprecated public void cancelLoadUrl() { this.cancelLoadUrl = true; } @@ -635,13 +424,7 @@ public class DroidGap extends Activity implements CordovaInterface { * Clear web history in this web view. */ public void clearHistory() { - this.urls.clear(); this.appView.clearHistory(); - - // Leave current url on history stack - if (this.url != null) { - this.urls.push(this.url); - } } /** @@ -650,22 +433,9 @@ public class DroidGap extends Activity implements CordovaInterface { * @return true if we went back, false if we are already at top */ public boolean backHistory() { - - // Check webview first to see if there is a history - // This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior) - if (this.appView.canGoBack()) { - this.appView.goBack(); - return true; + if (this.appView != null) { + return appView.backHistory(); } - - // If our managed history has prev url - if (this.urls.size() > 1) { - this.urls.pop(); // Pop current url - String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl() - this.loadUrl(url); - return true; - } - return false; } @@ -692,7 +462,18 @@ public class DroidGap extends Activity implements CordovaInterface { if (bundle == null) { return defaultValue; } - Boolean p = (Boolean)bundle.get(name); + Boolean p; + try { + p = (Boolean) bundle.get(name); + } catch (ClassCastException e) { + String s = bundle.get(name).toString(); + if ("true".equals(s)) { + p = true; + } + else { + p = false; + } + } if (p == null) { return defaultValue; } @@ -711,7 +492,12 @@ public class DroidGap extends Activity implements CordovaInterface { if (bundle == null) { return defaultValue; } - Integer p = (Integer)bundle.get(name); + Integer p; + try { + p = (Integer) bundle.get(name); + } catch (ClassCastException e) { + p = Integer.parseInt(bundle.get(name).toString()); + } if (p == null) { return defaultValue; } @@ -749,7 +535,12 @@ public class DroidGap extends Activity implements CordovaInterface { if (bundle == null) { return defaultValue; } - Double p = (Double)bundle.get(name); + Double p; + try { + p = (Double) bundle.get(name); + } catch (ClassCastException e) { + p = Double.parseDouble(bundle.get(name).toString()); + } if (p == null) { return defaultValue; } @@ -816,8 +607,8 @@ public class DroidGap extends Activity implements CordovaInterface { this.appView.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};"); // Forward to plugins - if (this.pluginManager != null) { - this.pluginManager.onPause(this.keepRunning); + if (this.appView.pluginManager != null) { + this.appView.pluginManager.onPause(this.keepRunning); } // If app doesn't want to run in background @@ -836,8 +627,8 @@ public class DroidGap extends Activity implements CordovaInterface { super.onNewIntent(intent); //Forward to plugins - if (this.pluginManager != null) { - this.pluginManager.onNewIntent(intent); + if ((this.appView != null) && (this.appView.pluginManager != null)) { + this.appView.pluginManager.onNewIntent(intent); } } @@ -861,8 +652,8 @@ public class DroidGap extends Activity implements CordovaInterface { this.appView.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};"); // Forward to plugins - if (this.pluginManager != null) { - this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); + if (this.appView.pluginManager != null) { + this.appView.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); } // If app doesn't want to run in background @@ -884,11 +675,11 @@ public class DroidGap extends Activity implements CordovaInterface { * The final call you receive before your activity is destroyed. */ public void onDestroy() { + LOG.d(TAG, "onDestroy()"); super.onDestroy(); if (this.appView != null) { - // Send destroy event to JavaScript this.appView.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};"); @@ -896,8 +687,8 @@ public class DroidGap extends Activity implements CordovaInterface { this.appView.loadUrl("about:blank"); // Forward to plugins - if (this.pluginManager != null) { - this.pluginManager.onDestroy(); + if (this.appView.pluginManager != null) { + this.appView.pluginManager.onDestroy(); } } else { @@ -912,10 +703,8 @@ public class DroidGap extends Activity implements CordovaInterface { * @param data The message data */ public void postMessage(String id, Object data) { - - // Forward to plugins - if (this.pluginManager != null) { - this.pluginManager.postMessage(id, data); + if (this.appView != null) { + this.appView.postMessage(id, data); } } @@ -928,10 +717,9 @@ public class DroidGap extends Activity implements CordovaInterface { * @param serviceType * @param className */ - @Deprecated public void addService(String serviceType, String className) { - if (this.pluginManager != null) { - this.pluginManager.addService(serviceType, className); + if (this.appView != null && this.appView.pluginManager != null) { + this.appView.pluginManager.addService(serviceType, className); } } @@ -942,66 +730,8 @@ public class DroidGap extends Activity implements CordovaInterface { * @param message */ public void sendJavascript(String statement) { - //We need to check for the null case on the Kindle Fire beacuse it changes the width and height on load - if(this.callbackServer != null) - this.callbackServer.sendJavascript(statement); - } - - /** - * Load the specified URL in the Cordova webview or a new browser instance. - * - * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded. - * - * @param url The url to load. - * @param openExternal Load url in browser instead of Cordova webview. - * @param clearHistory Clear the history stack, so new page becomes top of history - * @param params DroidGap parameters for new app - */ - public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap params) { //throws android.content.ActivityNotFoundException { - LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory); - - // If clearing history - if (clearHistory) { - this.clearHistory(); - } - - // If loading into our webview - if (!openExternal) { - - // Make sure url is in whitelist - if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || isUrlWhiteListed(url)) { - // TODO: What about params? - - // Clear out current url from history, since it will be replacing it - if (clearHistory) { - this.urls.clear(); - } - - // Load new URL - this.loadUrl(url); - } - // Load in default viewer if not - else { - LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL="+url+")"); - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - this.startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - LOG.e(TAG, "Error loading url "+url, e); - } - } - } - - // Load in default view intent - else { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - this.startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - LOG.e(TAG, "Error loading url "+url, e); - } + if (this.appView != null && this.appView.callbackServer != null) { + this.appView.callbackServer.sendJavascript(statement); } } @@ -1017,12 +747,12 @@ public class DroidGap extends Activity implements CordovaInterface { this.spinnerDialog = null; } final DroidGap me = this; - this.spinnerDialog = ProgressDialog.show(DroidGap.this, title , message, true, true, + this.spinnerDialog = ProgressDialog.show(DroidGap.this, title, message, true, true, new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - me.spinnerDialog = null; - } - }); + public void onCancel(DialogInterface dialog) { + me.spinnerDialog = null; + } + }); } /** @@ -1040,11 +770,11 @@ public class DroidGap extends Activity implements CordovaInterface { */ public void endActivity() { this.activityState = ACTIVITY_EXITING; - this.finish(); + super.finish(); } /** - * Called when a key is de-pressed. (Key UP) + * Called when a key is released. (Key UP) * * @param keyCode * @param event @@ -1091,24 +821,41 @@ public class DroidGap extends Activity implements CordovaInterface { } /** - * Any calls to Activity.startActivityForResult must use method below, so - * the result can be routed to them correctly. + * Called when a key is pressed. (Key DOWN) * - * 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 - * - * @throws RuntimeException + * @param keyCode + * @param event */ @Override - public void startActivityForResult(Intent intent, int requestCode) throws RuntimeException { - LOG.d(TAG, "DroidGap.startActivityForResult(intent,%d)", requestCode); - super.startActivityForResult(intent, requestCode); + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (this.appView == null) { + return super.onKeyDown(keyCode, event); + } + + // If volumedown key + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + if (this.volumedownBound==true) { + // only override default behaviour is event bound + LOG.d(TAG, "Down Key Hit"); + this.appView.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');"); + return true; + } + } + + // If volumeup key + else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + if (this.volumeupBound==true) { + // only override default behaviour is event bound + LOG.d(TAG, "Up Key Hit"); + this.appView.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');"); + return true; + } + } + return super.onKeyDown(keyCode, event); } /** - * Launch an activity for which you would like a result when it finished. When this activity exits, + * 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 @@ -1128,7 +875,7 @@ public class DroidGap extends Activity implements CordovaInterface { super.startActivityForResult(intent, requestCode); } - @Override + @Override /** * 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. @@ -1138,178 +885,91 @@ public class DroidGap extends Activity implements CordovaInterface { * @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); - } - } - - public void setActivityResultCallback(IPlugin 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. - */ - public void onReceivedError(final int errorCode, final String description, final String failingUrl) { - final DroidGap me = this; - - // If errorUrl specified, then load it - final String errorUrl = me.getStringProperty("errorUrl", null); - if ((errorUrl != null) && (errorUrl.startsWith("file://") || errorUrl.indexOf(me.baseUrl) == 0 || isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) { - - // Load URL on UI thread - me.runOnUiThread(new Runnable() { - public void run() { - me.showWebPage(errorUrl, false, true, null); - } - }); - } - // If not, then display error dialog - else { - final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP); - me.runOnUiThread(new Runnable() { - public void run() { - if(exit) - { - me.appView.setVisibility(View.GONE); - me.displayError("Application Error", description + " ("+failingUrl+")", "OK", exit); - } - } - }); - } - } - - /** - * Display an error dialog and optionally exit application. - * - * @param title - * @param message - * @param button - * @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.endActivity(); - } - } - }); - dlg.create(); - dlg.show(); - } - }); - } + 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); + } + } + public void setActivityResultCallback(IPlugin plugin) { + this.activityResultCallback = plugin; + } /** - * Load Cordova configuration from res/xml/cordova.xml. - * Approved list of URLs that can be loaded into DroidGap - * - * Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR) - * + * 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. */ - private void loadConfiguration() { - int id = getResources().getIdentifier("cordova", "xml", getPackageName()); - if (id == 0) { - LOG.i("CordovaLog", "cordova.xml missing. Ignoring..."); - return; + public void onReceivedError(final int errorCode, final String description, final String failingUrl) { + final DroidGap me = this; + + // Stop "app loading" spinner if showing + this.spinnerStop(); + + // If errorUrl specified, then load it + final String errorUrl = me.getStringProperty("errorUrl", null); + if ((errorUrl != null) && (errorUrl.startsWith("file://") || errorUrl.indexOf(me.baseUrl) == 0 || this.appView.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) { + + // Load URL on UI thread + me.runOnUiThread(new Runnable() { + public void run() { + me.appView.showWebPage(errorUrl, false, true, null); + } + }); } - XmlResourceParser xml = getResources().getXml(id); - int eventType = -1; - while (eventType != XmlResourceParser.END_DOCUMENT) { - if (eventType == XmlResourceParser.START_TAG) { - String strNode = xml.getName(); - if (strNode.equals("access")) { - String origin = xml.getAttributeValue(null, "origin"); - String subdomains = xml.getAttributeValue(null, "subdomains"); - if (origin != null) { - this.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); + // If not, then display error dialog + else { + final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP); + me.runOnUiThread(new Runnable() { + public void run() { + if (exit) + { + me.appView.setVisibility(View.GONE); + me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit); } } - else if (strNode.equals("log")) { - String level = xml.getAttributeValue(null, "level"); - LOG.i("CordovaLog", "Found log level %s", level); - if (level != null) { - LOG.setLogLevel(level); - } - } - else if (strNode.equals("preference")) { - String name = xml.getAttributeValue(null, "name"); - String value = xml.getAttributeValue(null, "value"); - String readonlyString = xml.getAttributeValue(null, "readonly"); - - boolean readonly = (readonlyString != null && - readonlyString.equals("true")); - - LOG.i("CordovaLog", "Found preference for %s", name); - - preferences.add(new PreferenceNode(name, value, readonly)); - } - } - try { - eventType = xml.next(); - } catch (XmlPullParserException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } + }); } } /** - * Add entry to approved list of URLs (whitelist) + * Display an error dialog and optionally exit application. * - * @param origin URL regular expression to allow - * @param subdomains T=include all subdomains under origin + * @param title + * @param message + * @param button + * @param exit */ - private void addWhiteListEntry(String origin, boolean subdomains) { - try { - // Unlimited access to network resources - if(origin.compareTo("*") == 0) { - LOG.d(TAG, "Unlimited access to network resources"); - whiteList.add(Pattern.compile(".*")); - } else { // specific access - // check if subdomains should be included - // TODO: we should not add more domains if * has already been added - if (subdomains) { - // XXX making it stupid friendly for people who forget to include protocol/SSL - if(origin.startsWith("http")) { - whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?"))); - } else { - whiteList.add(Pattern.compile("^https?://(.*\\.)?"+origin)); - } - LOG.d(TAG, "Origin to allow with subdomains: %s", origin); - } else { - // XXX making it stupid friendly for people who forget to include protocol/SSL - if(origin.startsWith("http")) { - whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://"))); - } else { - whiteList.add(Pattern.compile("^https?://"+origin)); - } - LOG.d(TAG, "Origin to allow: %s", origin); - } - } - } catch(Exception e) { - LOG.d(TAG, "Failed to add origin %s", origin); - } + 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() { + try { + 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.endActivity(); + } + } + }); + dlg.create(); + dlg.show(); + } catch (Exception e) { + finish(); + } + } + }); } /** @@ -1319,56 +979,17 @@ public class DroidGap extends Activity implements CordovaInterface { * @return */ public boolean isUrlWhiteListed(String url) { - // Check to see if we have matched url previously - if (whiteListCache.get(url) != null) { - return true; - } - - // Look for match in white list - Iterator pit = whiteList.iterator(); - while (pit.hasNext()) { - Pattern p = pit.next(); - Matcher m = p.matcher(url); - - // If match found, then cache it to speed up subsequent comparisons - if (m.find()) { - whiteListCache.put(url, true); - return true; - } + if (this.appView != null) { + return this.appView.isUrlWhiteListed(url); } return false; } - /* - * URL stack manipulators - */ - - /** - * Returns the top url on the stack without removing it from - * the stack. - */ - public String peekAtUrlStack() { - if (urls.size() > 0) { - return urls.peek(); - } - return ""; - } - - /** - * Add a url to the stack - * - * @param url - */ - public void pushUrl(String url) { - urls.push(url); - } - /* * Hook in DroidGap for menu plugins * */ - @Override public boolean onCreateOptionsMenu(Menu menu) { this.postMessage("onCreateOptionsMenu", menu); @@ -1387,18 +1008,57 @@ public class DroidGap extends Activity implements CordovaInterface { return true; } + /** + * Get Activity context. + * + * @return + */ public Context getContext() { - return this; + return this.getContext(); } + /** + * Override the backbutton. + * + * @param override + */ public void bindBackButton(boolean override) { - // TODO Auto-generated method stub - this.bound = override; + this.bound = override; } + /** + * Determine of backbutton is overridden. + * + * @return + */ public boolean isBackButtonBound() { + return this.bound; + } + + /** + * Load the specified URL in the Cordova webview or a new browser instance. + * + * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded. + * + * @param url The url to load. + * @param openExternal Load url in browser instead of Cordova webview. + * @param clearHistory Clear the history stack, so new page becomes top of history + * @param params DroidGap parameters for new app + */ + public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap params) { + if (this.appView != null) { + appView.showWebPage(url, openExternal, clearHistory, params); + } + } + + public void bindButton(String button, boolean override) { // TODO Auto-generated method stub - return this.bound; + if (button.compareTo("volumeup")==0) { + this.volumeupBound = override; + } + else if (button.compareTo("volumedown")==0) { + this.volumedownBound = override; + } } protected Dialog splashDialog; @@ -1416,7 +1076,9 @@ public class DroidGap extends Activity implements CordovaInterface { /** * Shows the splash screen over the full Activity */ + @SuppressWarnings("deprecation") protected void showSplashScreen(int time) { + // Get reference to display Display display = getWindowManager().getDefaultDisplay(); @@ -1432,6 +1094,12 @@ public class DroidGap extends Activity implements CordovaInterface { // Create and show the dialog splashDialog = new Dialog(this, android.R.style.Theme_Translucent_NoTitleBar); + // check to see if the splash screen should be full screen + if ((getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) + == WindowManager.LayoutParams.FLAG_FULLSCREEN) { + splashDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } splashDialog.setContentView(root); splashDialog.setCancelable(false); splashDialog.show(); @@ -1444,4 +1112,43 @@ public class DroidGap extends Activity implements CordovaInterface { } }, time); } + + /** + * Called when a message is sent to plugin. + * + * @param id The message id + * @param data The message data + * @return Object or null + */ + public Object onMessage(String id, Object data) { + LOG.d(TAG, "onMessage(" + id + "," + data + ")"); + if ("splashscreen".equals(id)) { + if ("hide".equals(data.toString())) { + this.removeSplashScreen(); + } + else { + this.splashscreen = this.getIntegerProperty("splashscreen", 0); + this.showSplashScreen(this.splashscreenTime); + } + } + else if ("spinner".equals(id)) { + if ("stop".equals(data.toString())) { + this.spinnerStop(); + this.appView.setVisibility(View.VISIBLE); + } + } + else if ("onReceivedError".equals(id)) { + JSONObject d = (JSONObject) data; + try { + this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url")); + } catch (JSONException e) { + e.printStackTrace(); + } + } + else if ("exit".equals(id)) { + this.endActivity(); + } + return null; + } + } diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index c9b13a5c..0f285ae1 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -51,7 +51,6 @@ import android.net.Uri; import android.util.Log; import android.webkit.CookieManager; - public class FileTransfer extends Plugin { private static final String LOG_TAG = "FileTransfer"; @@ -76,8 +75,7 @@ public class FileTransfer extends Plugin { try { source = args.getString(0); target = args.getString(1); - } - catch (JSONException e) { + } catch (JSONException e) { Log.d(LOG_TAG, "Missing source or target"); return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target"); } @@ -353,11 +351,11 @@ public class FileTransfer extends Plugin { } public void checkClientTrusted(X509Certificate[] chain, - String authType) throws CertificateException { + String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, - String authType) throws CertificateException { + String authType) throws CertificateException { } } }; @@ -419,7 +417,7 @@ public class FileTransfer extends Plugin { */ private String getArgument(JSONArray args, int position, String defaultString) { String arg = defaultString; - if(args.length() >= position) { + if (args.length() >= position) { arg = args.optString(position); if (arg == null || "null".equals(arg)) { arg = defaultString; @@ -428,7 +426,6 @@ public class FileTransfer extends Plugin { return arg; } - /** * Downloads a file form a given URL and saves it to the specified directory. * @@ -447,7 +444,7 @@ public class FileTransfer extends Plugin { file.getParentFile().mkdirs(); // connect to server - if(this.ctx.isUrlWhiteListed(source)) + if (webView.isUrlWhiteListed(source)) { URL url = new URL(source); connection = (HttpURLConnection) url.openConnection(); @@ -464,20 +461,24 @@ public class FileTransfer extends Plugin { Log.d(LOG_TAG, "Download file: " + url); - InputStream inputStream = connection.getInputStream(); - byte[] buffer = new byte[1024]; - int bytesRead = 0; + connection.connect(); - FileOutputStream outputStream = new FileOutputStream(file); + Log.d(LOG_TAG, "Download file:" + url); - // write bytes to file - while ( (bytesRead = inputStream.read(buffer)) > 0 ) { - outputStream.write(buffer,0, bytesRead); - } + InputStream inputStream = connection.getInputStream(); + byte[] buffer = new byte[1024]; + int bytesRead = 0; - outputStream.close(); + FileOutputStream outputStream = new FileOutputStream(file); - Log.d(LOG_TAG, "Saved file: " + target); + // write bytes to file + while ((bytesRead = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, bytesRead); + } + + outputStream.close(); + + Log.d(LOG_TAG, "Saved file: " + target); // create FileEntry object FileUtils fileUtil = new FileUtils(); @@ -521,7 +522,7 @@ public class FileTransfer extends Plugin { private InputStream getPathFromUri(String path) throws FileNotFoundException { if (path.startsWith("content:")) { Uri uri = Uri.parse(path); - return ctx.getContentResolver().openInputStream(uri); + return ctx.getActivity().getContentResolver().openInputStream(uri); } else if (path.startsWith("file://")) { int question = path.indexOf("?"); diff --git a/framework/src/org/apache/cordova/FileUtils.java b/framework/src/org/apache/cordova/FileUtils.java index 4d2b31e4..825fabe2 100755 --- a/framework/src/org/apache/cordova/FileUtils.java +++ b/framework/src/org/apache/cordova/FileUtils.java @@ -37,18 +37,21 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +//import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.webkit.MimeTypeMap; +//import android.app.Activity; /** * This class provides SD card file and directory services to JavaScript. * Only files on the SD card can be accessed. */ public class FileUtils extends Plugin { + @SuppressWarnings("unused") private static final String LOG_TAG = "FileUtils"; private static final String _DATA = "_data"; // The column name where the file path is stored @@ -129,7 +132,7 @@ public class FileUtils extends Plugin { else if (action.equals("requestFileSystem")) { long size = args.optLong(1); if (size != 0) { - if (size > (DirectoryManager.getFreeDiskSpace(true)*1024)) { + if (size > (DirectoryManager.getFreeDiskSpace(true) * 1024)) { return new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR); } } @@ -220,9 +223,9 @@ public class FileUtils extends Plugin { */ private void notifyDelete(String filePath) { String newFilePath = stripFileProtocol(filePath); - int result = this.ctx.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - MediaStore.Images.Media.DATA + " = ?", - new String[] {newFilePath}); + int result = this.ctx.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + MediaStore.Images.Media.DATA + " = ?", + new String[] { filePath }); } /** @@ -235,6 +238,7 @@ public class FileUtils extends Plugin { * @throws IOException if the user can't read the file * @throws JSONException */ + @SuppressWarnings("deprecation") private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException { String decoded = URLDecoder.decode(url, "UTF-8"); @@ -242,7 +246,7 @@ public class FileUtils extends Plugin { // Handle the special case where you get an Android content:// uri. if (decoded.startsWith("content:")) { - Cursor cursor = this.ctx.managedQuery(Uri.parse(decoded), new String[] { MediaStore.Images.Media.DATA }, null, null, null); + Cursor cursor = this.ctx.getActivity().managedQuery(Uri.parse(decoded), new String[] { MediaStore.Images.Media.DATA }, null, null, null); // Note: MediaStore.Images/Audio/Video.Media.DATA is always "_data" int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); @@ -293,7 +297,7 @@ public class FileUtils extends Plugin { if (fp.isDirectory()) { File[] files = fp.listFiles(); - for (int i=0; i= size) { - FileChannel channel = raf.getChannel(); - channel.truncate(size); - return size; + FileChannel channel = raf.getChannel(); + channel.truncate(size); + return size; } return raf.length(); @@ -1023,7 +1026,7 @@ public class FileUtils extends Plugin { private InputStream getPathFromUri(String path) throws FileNotFoundException { if (path.startsWith("content")) { Uri uri = Uri.parse(path); - return ctx.getContentResolver().openInputStream(uri); + return ctx.getActivity().getContentResolver().openInputStream(uri); } else { path = stripFileProtocol(path); @@ -1035,12 +1038,13 @@ public class FileUtils extends Plugin { * 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 + * @param ctx) the current applicaiton context * @return the full path to the file */ + @SuppressWarnings("deprecation") protected static String getRealPathFromURI(Uri contentUri, CordovaInterface ctx) { String[] proj = { _DATA }; - Cursor cursor = ctx.managedQuery(contentUri, proj, null, null, null); + Cursor cursor = ctx.getActivity().managedQuery(contentUri, proj, null, null, null); int column_index = cursor.getColumnIndexOrThrow(_DATA); cursor.moveToFirst(); return cursor.getString(column_index); diff --git a/framework/src/org/apache/cordova/GeoBroker.java b/framework/src/org/apache/cordova/GeoBroker.java index 6f77845b..e53358e3 100755 --- a/framework/src/org/apache/cordova/GeoBroker.java +++ b/framework/src/org/apache/cordova/GeoBroker.java @@ -55,7 +55,7 @@ public class GeoBroker extends Plugin { */ public PluginResult execute(String action, JSONArray args, String callbackId) { if (this.locationManager == null) { - this.locationManager = (LocationManager) this.ctx.getSystemService(Context.LOCATION_SERVICE); + this.locationManager = (LocationManager) this.ctx.getActivity().getSystemService(Context.LOCATION_SERVICE); this.networkListener = new NetworkListener(this.locationManager, this); this.gpsListener = new GPSListener(this.locationManager, this); } @@ -141,46 +141,47 @@ public class GeoBroker extends Plugin { o.put("latitude", loc.getLatitude()); o.put("longitude", loc.getLongitude()); o.put("altitude", (loc.hasAltitude() ? loc.getAltitude() : null)); - o.put("accuracy", loc.getAccuracy()); - o.put("heading", (loc.hasBearing() ? (loc.hasSpeed() ? loc.getBearing() : null) : null)); - o.put("speed", loc.getSpeed()); + o.put("accuracy", loc.getAccuracy()); + o.put("heading", (loc.hasBearing() ? (loc.hasSpeed() ? loc.getBearing() : null) : null)); + o.put("speed", loc.getSpeed()); o.put("timestamp", loc.getTime()); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } - return o; - } - public void win(Location loc, String callbackId) { - PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(loc)); - this.success(result, callbackId); - } - /** - * Location failed. Send error back to JavaScript. - * - * @param code The error code - * @param msg The error message - * @throws JSONException - */ - public void fail(int code, String msg, String callbackId) { - JSONObject obj = new JSONObject(); - String backup = null; - try { - obj.put("code", code); - obj.put("message", msg); - } catch (JSONException e) { - obj = null; - backup = "{'code':" + code + ",'message':'" + msg.replaceAll("'", "\'") + "'}"; - } - PluginResult result; - if (obj != null) { - result = new PluginResult(PluginResult.Status.ERROR, obj); - } else { - result = new PluginResult(PluginResult.Status.ERROR, backup); - } + } - this.error(result, callbackId); - } + public void win(Location loc, String callbackId) { + PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(loc)); + this.success(result, callbackId); + } + + /** + * Location failed. Send error back to JavaScript. + * + * @param code The error code + * @param msg The error message + * @throws JSONException + */ + public void fail(int code, String msg, String callbackId) { + JSONObject obj = new JSONObject(); + String backup = null; + try { + obj.put("code", code); + obj.put("message", msg); + } catch (JSONException e) { + obj = null; + backup = "{'code':" + code + ",'message':'" + msg.replaceAll("'", "\'") + "'}"; + } + PluginResult result; + if (obj != null) { + result = new PluginResult(PluginResult.Status.ERROR, obj); + } else { + result = new PluginResult(PluginResult.Status.ERROR, backup); + } + + this.error(result, callbackId); + } } diff --git a/framework/src/org/apache/cordova/HttpHandler.java b/framework/src/org/apache/cordova/HttpHandler.java index 3ced2def..1de8302e 100755 --- a/framework/src/org/apache/cordova/HttpHandler.java +++ b/framework/src/org/apache/cordova/HttpHandler.java @@ -34,10 +34,16 @@ public class HttpHandler { HttpEntity entity = getHttpEntity(url); try { writeToDisk(entity, file); - } catch (Exception e) { e.printStackTrace(); return false; } + } catch (Exception e) { + e.printStackTrace(); + return false; + } try { entity.consumeContent(); - } catch (Exception e) { e.printStackTrace(); return false; } + } catch (Exception e) { + e.printStackTrace(); + return false; + } return true; } @@ -46,7 +52,7 @@ public class HttpHandler { * get the http entity at a given url */ { - HttpEntity entity=null; + HttpEntity entity = null; try { DefaultHttpClient httpclient = new DefaultHttpClient(); HttpGet httpget = new HttpGet(url); @@ -61,18 +67,18 @@ public class HttpHandler { * writes a HTTP entity to the specified filename and location on disk */ { - int i=0; - String FilePath="/sdcard/" + file; + //int i = 0; + String FilePath = "/sdcard/" + file; InputStream in = entity.getContent(); byte buff[] = new byte[1024]; - FileOutputStream out= - new FileOutputStream(FilePath); - do { + FileOutputStream out = + new FileOutputStream(FilePath); + do { int numread = in.read(buff); if (numread <= 0) break; out.write(buff, 0, numread); - i++; + //i++; } while (true); out.flush(); out.close(); diff --git a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java index c6d93532..280269b8 100755 --- a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java +++ b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java @@ -17,9 +17,11 @@ under the License. */ package org.apache.cordova; + import org.apache.cordova.api.LOG; import android.content.Context; +//import android.view.View.MeasureSpec; import android.widget.LinearLayout; /** @@ -75,7 +77,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout { LOG.d(TAG, "Ignore this event"); } // Account for orientation change and ignore this event/Fire orientation change - else if(screenHeight == width) + else if (screenHeight == width) { int tmp_var = screenHeight; screenHeight = screenWidth; @@ -85,14 +87,14 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout { // If the height as gotten bigger then we will assume the soft keyboard has // gone away. else if (height > oldHeight) { - if(app != null) - app.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');"); + if (app != null) + app.appView.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');"); } - // If the height as gotten smaller then we will assume the soft keyboard has + // If the height as gotten smaller then we will assume the soft keyboard has // been displayed. else if (height < oldHeight) { - if(app != null) - app.sendJavascript("cordova.fireDocumentEvent('showkeyboard');"); + if (app != null) + app.appView.sendJavascript("cordova.fireDocumentEvent('showkeyboard');"); } // Update the old height for the next event diff --git a/framework/src/org/apache/cordova/NetworkManager.java b/framework/src/org/apache/cordova/NetworkManager.java index 8b50869f..4661ddb6 100755 --- a/framework/src/org/apache/cordova/NetworkManager.java +++ b/framework/src/org/apache/cordova/NetworkManager.java @@ -23,7 +23,6 @@ import org.apache.cordova.api.Plugin; import org.apache.cordova.api.PluginResult; import org.json.JSONArray; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -77,7 +76,7 @@ public class NetworkManager extends Plugin { /** * Constructor. */ - public NetworkManager() { + public NetworkManager() { this.receiver = null; } @@ -89,20 +88,21 @@ public class NetworkManager extends Plugin { */ public void setContext(CordovaInterface ctx) { super.setContext(ctx); - this.sockMan = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + this.sockMan = (ConnectivityManager) ctx.getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); this.connectionCallbackId = null; // We need to listen to connectivity events to update navigator.connection - IntentFilter intentFilter = new IntentFilter() ; + IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); if (this.receiver == null) { this.receiver = new BroadcastReceiver() { + @SuppressWarnings("deprecation") @Override public void onReceive(Context context, Intent intent) { updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)); } }; - ctx.registerReceiver(this.receiver, intentFilter); + ctx.getActivity().registerReceiver(this.receiver, intentFilter); } } @@ -146,7 +146,7 @@ public class NetworkManager extends Plugin { public void onDestroy() { if (this.receiver != null) { try { - this.ctx.unregisterReceiver(this.receiver); + this.ctx.getActivity().unregisterReceiver(this.receiver); } catch (Exception e) { Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e); } @@ -157,7 +157,6 @@ public class NetworkManager extends Plugin { // LOCAL METHODS //-------------------------------------------------------------------------- - /** * Updates the JavaScript side whenever the connection changes * @@ -200,7 +199,7 @@ public class NetworkManager extends Plugin { this.success(result, this.connectionCallbackId); // Send to all plugins - this.ctx.postMessage("networkconnection", type); + webView.postMessage("networkconnection", type); } /** @@ -224,7 +223,7 @@ public class NetworkManager extends Plugin { return TYPE_2G; } else if (type.toLowerCase().startsWith(CDMA) || - type.toLowerCase().equals(UMTS) || + type.toLowerCase().equals(UMTS) || type.toLowerCase().equals(ONEXRTT) || type.toLowerCase().equals(EHRPD) || type.toLowerCase().equals(HSUPA) || diff --git a/framework/src/org/apache/cordova/Notification.java b/framework/src/org/apache/cordova/Notification.java index 4c5b9298..ed0b4365 100755 --- a/framework/src/org/apache/cordova/Notification.java +++ b/framework/src/org/apache/cordova/Notification.java @@ -37,330 +37,330 @@ import android.os.Vibrator; */ public class Notification extends Plugin { - public int confirmResult = -1; - public ProgressDialog spinnerDialog = null; - public ProgressDialog progressDialog = null; + public int confirmResult = -1; + public ProgressDialog spinnerDialog = null; + public ProgressDialog progressDialog = null; - /** - * Constructor. - */ - public Notification() { - } + /** + * Constructor. + */ + public Notification() { + } - /** - * 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 = ""; + /** + * 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 = ""; - try { - if (action.equals("beep")) { - this.beep(args.getLong(0)); - } - else if (action.equals("vibrate")) { - this.vibrate(args.getLong(0)); - } - else if (action.equals("alert")) { - this.alert(args.getString(0),args.getString(1),args.getString(2), callbackId); - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - return r; - } - else if (action.equals("confirm")) { - this.confirm(args.getString(0),args.getString(1),args.getString(2), callbackId); - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - return r; - } - else if (action.equals("activityStart")) { - this.activityStart(args.getString(0),args.getString(1)); - } - else if (action.equals("activityStop")) { - this.activityStop(); - } - else if (action.equals("progressStart")) { - this.progressStart(args.getString(0),args.getString(1)); - } - else if (action.equals("progressValue")) { - this.progressValue(args.getInt(0)); - } - else if (action.equals("progressStop")) { - this.progressStop(); - } - return new PluginResult(status, result); - } catch (JSONException e) { - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + try { + if (action.equals("beep")) { + this.beep(args.getLong(0)); + } + else if (action.equals("vibrate")) { + this.vibrate(args.getLong(0)); + } + else if (action.equals("alert")) { + this.alert(args.getString(0), args.getString(1), args.getString(2), callbackId); + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + } + else if (action.equals("confirm")) { + this.confirm(args.getString(0), args.getString(1), args.getString(2), callbackId); + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + } + else if (action.equals("activityStart")) { + this.activityStart(args.getString(0), args.getString(1)); + } + else if (action.equals("activityStop")) { + this.activityStop(); + } + else if (action.equals("progressStart")) { + this.progressStart(args.getString(0), args.getString(1)); + } + else if (action.equals("progressValue")) { + this.progressValue(args.getInt(0)); + } + else if (action.equals("progressStop")) { + this.progressStop(); + } + return new PluginResult(status, result); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } } - } - /** - * Identifies if action to be executed returns a value and should be run synchronously. - * - * @param action The action to execute - * @return T=returns value - */ - public boolean isSynch(String action) { - if (action.equals("alert")) { - return true; + /** + * Identifies if action to be executed returns a value and should be run synchronously. + * + * @param action The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + if (action.equals("alert")) { + return true; + } + else if (action.equals("confirm")) { + return true; + } + else if (action.equals("activityStart")) { + return true; + } + else if (action.equals("activityStop")) { + return true; + } + else if (action.equals("progressStart")) { + return true; + } + else if (action.equals("progressValue")) { + return true; + } + else if (action.equals("progressStop")) { + return true; + } + else { + return false; + } } - else if (action.equals("confirm")) { - return true; - } - else if (action.equals("activityStart")) { - return true; - } - else if (action.equals("activityStop")) { - return true; - } - else if (action.equals("progressStart")) { - return true; - } - else if (action.equals("progressValue")) { - return true; - } - else if (action.equals("progressStop")) { - return true; - } - else { - return false; - } - } //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- - /** - * Beep plays the default notification ringtone. - * - * @param count Number of times to play notification - */ - public void beep(long count) { - Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - Ringtone notification = RingtoneManager.getRingtone(this.ctx.getContext(), ringtone); + /** + * Beep plays the default notification ringtone. + * + * @param count Number of times to play notification + */ + public void beep(long count) { + Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + Ringtone notification = RingtoneManager.getRingtone(this.ctx.getActivity().getBaseContext(), ringtone); - // If phone is not set to silent mode - if (notification != null) { - for (long i = 0; i < count; ++i) { - notification.play(); - long timeout = 5000; - while (notification.isPlaying() && (timeout > 0)) { - timeout = timeout - 100; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } + // If phone is not set to silent mode + if (notification != null) { + for (long i = 0; i < count; ++i) { + notification.play(); + long timeout = 5000; + while (notification.isPlaying() && (timeout > 0)) { + timeout = timeout - 100; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + } } - } } - } - /** - * Vibrates the device for the specified amount of time. - * - * @param time Time to vibrate in ms. - */ - public void vibrate(long time){ + /** + * Vibrates the device for the specified amount of time. + * + * @param time Time to vibrate in ms. + */ + public void vibrate(long time) { // Start the vibration, 0 defaults to half a second. - if (time == 0) { - time = 500; - } - Vibrator vibrator = (Vibrator) this.ctx.getSystemService(Context.VIBRATOR_SERVICE); + if (time == 0) { + time = 500; + } + Vibrator vibrator = (Vibrator) this.ctx.getActivity().getSystemService(Context.VIBRATOR_SERVICE); vibrator.vibrate(time); - } + } - /** - * Builds and shows a native Android alert with given Strings - * @param message The message the alert should display - * @param title The title of the alert - * @param buttonLabel The label of the button - * @param callbackId The callback id - */ - public synchronized void alert(final String message, final String title, final String buttonLabel, final String callbackId) { + /** + * Builds and shows a native Android alert with given Strings + * @param message The message the alert should display + * @param title The title of the alert + * @param buttonLabel The label of the button + * @param callbackId The callback id + */ + public synchronized void alert(final String message, final String title, final String buttonLabel, final String callbackId) { - final CordovaInterface ctx = this.ctx; - final Notification notification = this; + final CordovaInterface ctx = this.ctx; + final Notification notification = this; - Runnable runnable = new Runnable() { - public void run() { + Runnable runnable = new Runnable() { + public void run() { - AlertDialog.Builder dlg = new AlertDialog.Builder(ctx.getContext()); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(false); - dlg.setPositiveButton(buttonLabel, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - notification.success(new PluginResult(PluginResult.Status.OK, 0), callbackId); - } - }); - dlg.create(); - dlg.show(); - }; - }; - this.ctx.runOnUiThread(runnable); - } + AlertDialog.Builder dlg = new AlertDialog.Builder(ctx.getActivity()); + dlg.setMessage(message); + dlg.setTitle(title); + dlg.setCancelable(false); + dlg.setPositiveButton(buttonLabel, + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + notification.success(new PluginResult(PluginResult.Status.OK, 0), callbackId); + } + }); + dlg.create(); + dlg.show(); + }; + }; + this.ctx.getActivity().runOnUiThread(runnable); + } - /** - * Builds and shows a native Android confirm dialog with given title, message, buttons. - * This dialog only shows up to 3 buttons. Any labels after that will be ignored. - * The index of the button pressed will be returned to the JavaScript callback identified by callbackId. - * - * @param message The message the dialog should display - * @param title The title of the dialog - * @param buttonLabels A comma separated list of button labels (Up to 3 buttons) - * @param callbackId The callback id - */ - public synchronized void confirm(final String message, final String title, String buttonLabels, final String callbackId) { + /** + * Builds and shows a native Android confirm dialog with given title, message, buttons. + * This dialog only shows up to 3 buttons. Any labels after that will be ignored. + * The index of the button pressed will be returned to the JavaScript callback identified by callbackId. + * + * @param message The message the dialog should display + * @param title The title of the dialog + * @param buttonLabels A comma separated list of button labels (Up to 3 buttons) + * @param callbackId The callback id + */ + public synchronized void confirm(final String message, final String title, String buttonLabels, final String callbackId) { - final CordovaInterface ctx = this.ctx; - final Notification notification = this; - final String[] fButtons = buttonLabels.split(","); + final CordovaInterface ctx = this.ctx; + final Notification notification = this; + final String[] fButtons = buttonLabels.split(","); - Runnable runnable = new Runnable() { - public void run() { - AlertDialog.Builder dlg = new AlertDialog.Builder(ctx.getContext()); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(false); + Runnable runnable = new Runnable() { + public void run() { + AlertDialog.Builder dlg = new AlertDialog.Builder(ctx.getActivity()); + dlg.setMessage(message); + dlg.setTitle(title); + dlg.setCancelable(false); - // First button - if (fButtons.length > 0) { - dlg.setNegativeButton(fButtons[0], - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - notification.success(new PluginResult(PluginResult.Status.OK, 1), callbackId); - } - }); + // First button + if (fButtons.length > 0) { + dlg.setNegativeButton(fButtons[0], + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + notification.success(new PluginResult(PluginResult.Status.OK, 1), callbackId); + } + }); + } + + // Second button + if (fButtons.length > 1) { + dlg.setNeutralButton(fButtons[1], + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + notification.success(new PluginResult(PluginResult.Status.OK, 2), callbackId); + } + }); + } + + // Third button + if (fButtons.length > 2) { + dlg.setPositiveButton(fButtons[2], + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + notification.success(new PluginResult(PluginResult.Status.OK, 3), callbackId); + } + } + ); + } + + dlg.create(); + dlg.show(); + }; + }; + this.ctx.getActivity().runOnUiThread(runnable); + } + + /** + * Show the spinner. + * + * @param title Title of the dialog + * @param message The message of the dialog + */ + public synchronized void activityStart(final String title, final String message) { + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; } - - // Second button - if (fButtons.length > 1) { - dlg.setNeutralButton(fButtons[1], - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - notification.success(new PluginResult(PluginResult.Status.OK, 2), callbackId); + final Notification notification = this; + final CordovaInterface ctx = this.ctx; + Runnable runnable = new Runnable() { + public void run() { + notification.spinnerDialog = ProgressDialog.show(ctx.getActivity(), title, message, true, true, + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + notification.spinnerDialog = null; + } + }); } - }); + }; + this.ctx.getActivity().runOnUiThread(runnable); + } + + /** + * Stop spinner. + */ + public synchronized void activityStop() { + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; } + } - // Third button - if (fButtons.length > 2) { - dlg.setPositiveButton(fButtons[2], - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - notification.success(new PluginResult(PluginResult.Status.OK, 3), callbackId); - } - } - ); + /** + * Show the progress dialog. + * + * @param title Title of the dialog + * @param message The message of the dialog + */ + public synchronized void progressStart(final String title, final String message) { + if (this.progressDialog != null) { + this.progressDialog.dismiss(); + this.progressDialog = null; } - - dlg.create(); - dlg.show(); - }; - }; - this.ctx.runOnUiThread(runnable); - } - - /** - * Show the spinner. - * - * @param title Title of the dialog - * @param message The message of the dialog - */ - public synchronized void activityStart(final String title, final String message) { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } - final Notification notification = this; - final CordovaInterface ctx = this.ctx; - Runnable runnable = new Runnable() { - public void run() { - notification.spinnerDialog = ProgressDialog.show(ctx.getContext(), title , message, true, true, - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - notification.spinnerDialog = null; + final Notification notification = this; + final CordovaInterface ctx = this.ctx; + Runnable runnable = new Runnable() { + public void run() { + notification.progressDialog = new ProgressDialog(ctx.getActivity()); + notification.progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + notification.progressDialog.setTitle(title); + notification.progressDialog.setMessage(message); + notification.progressDialog.setCancelable(true); + notification.progressDialog.setMax(100); + notification.progressDialog.setProgress(0); + notification.progressDialog.setOnCancelListener( + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + notification.progressDialog = null; + } + }); + notification.progressDialog.show(); } - }); + }; + this.ctx.getActivity().runOnUiThread(runnable); + } + + /** + * Set value of progress bar. + * + * @param value 0-100 + */ + public synchronized void progressValue(int value) { + if (this.progressDialog != null) { + this.progressDialog.setProgress(value); } - }; - this.ctx.runOnUiThread(runnable); - } - - /** - * Stop spinner. - */ - public synchronized void activityStop() { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; } - } - /** - * Show the progress dialog. - * - * @param title Title of the dialog - * @param message The message of the dialog - */ - public synchronized void progressStart(final String title, final String message) { - if (this.progressDialog != null) { - this.progressDialog.dismiss(); - this.progressDialog = null; + /** + * Stop progress dialog. + */ + public synchronized void progressStop() { + if (this.progressDialog != null) { + this.progressDialog.dismiss(); + this.progressDialog = null; + } } - final Notification notification = this; - final CordovaInterface ctx = this.ctx; - Runnable runnable = new Runnable() { - public void run() { - notification.progressDialog = new ProgressDialog(ctx.getContext()); - notification.progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - notification.progressDialog.setTitle(title); - notification.progressDialog.setMessage(message); - notification.progressDialog.setCancelable(true); - notification.progressDialog.setMax(100); - notification.progressDialog.setProgress(0); - notification.progressDialog.setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - notification.progressDialog = null; - } - }); - notification.progressDialog.show(); - } - }; - this.ctx.runOnUiThread(runnable); - } - - /** - * Set value of progress bar. - * - * @param value 0-100 - */ - public synchronized void progressValue(int value) { - if (this.progressDialog != null) { - this.progressDialog.setProgress(value); - } - } - - /** - * Stop progress dialog. - */ - public synchronized void progressStop() { - if (this.progressDialog != null) { - this.progressDialog.dismiss(); - this.progressDialog = null; - } - } } diff --git a/framework/src/org/apache/cordova/PreferenceSet.java b/framework/src/org/apache/cordova/PreferenceSet.java deleted file mode 100644 index 93219554..00000000 --- a/framework/src/org/apache/cordova/PreferenceSet.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. -*/ -package org.apache.cordova; - -import java.util.HashSet; - -import org.apache.cordova.PreferenceNode; - - -public class PreferenceSet { - private HashSet innerSet; - - public PreferenceSet() { - this.innerSet = new HashSet(); - } - - public void add(PreferenceNode node) { - this.innerSet.add(node); - } - - public int size() { - return this.innerSet.size(); - } - - public void clear() { - this.innerSet.clear(); - } - - public String pref(String prefName) { - for (PreferenceNode n : innerSet) - if (prefName.equals(n.name)) - return n.value; - - return null; - } - - public boolean prefMatches(String prefName, String prefValue) { - String value = pref(prefName); - - if (value == null) { - return false; - } else { - return value.equals(prefValue); - } - } -} diff --git a/framework/src/org/apache/cordova/SplashScreen.java b/framework/src/org/apache/cordova/SplashScreen.java index a53bb714..9fca9b25 100644 --- a/framework/src/org/apache/cordova/SplashScreen.java +++ b/framework/src/org/apache/cordova/SplashScreen.java @@ -31,7 +31,8 @@ public class SplashScreen extends Plugin { String result = ""; if (action.equals("hide")) { - ((DroidGap)this.ctx).removeSplashScreen(); + //((DroidGap)this.ctx).removeSplashScreen(); + this.webView.postMessage("splashscreen", "hide"); } else { status = PluginResult.Status.INVALID_ACTION; diff --git a/framework/src/org/apache/cordova/Storage.java b/framework/src/org/apache/cordova/Storage.java index 18fbcaff..ef87b45a 100755 --- a/framework/src/org/apache/cordova/Storage.java +++ b/framework/src/org/apache/cordova/Storage.java @@ -141,7 +141,7 @@ public class Storage extends Plugin { // If no database path, generate from application package if (this.path == null) { - this.path = this.ctx.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); + this.path = this.ctx.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); } this.dbName = this.path + File.pathSeparator + db + ".db"; diff --git a/framework/src/org/apache/cordova/TempListener.java b/framework/src/org/apache/cordova/TempListener.java index f878256b..21c0de01 100755 --- a/framework/src/org/apache/cordova/TempListener.java +++ b/framework/src/org/apache/cordova/TempListener.java @@ -25,7 +25,6 @@ import org.apache.cordova.api.Plugin; import org.apache.cordova.api.PluginResult; import org.json.JSONArray; - import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -51,12 +50,12 @@ public class TempListener extends Plugin implements SensorEventListener { */ public void setContext(CordovaInterface ctx) { super.setContext(ctx); - this.sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE); + this.sensorManager = (SensorManager) ctx.getActivity().getSystemService(Context.SENSOR_SERVICE); } /** * 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. @@ -87,7 +86,8 @@ public class TempListener extends Plugin implements SensorEventListener { // LOCAL METHODS //-------------------------------------------------------------------------- - public void start() { + public void start() { + @SuppressWarnings("deprecation") List list = this.sensorManager.getSensorList(Sensor.TYPE_TEMPERATURE); if (list.size() > 0) { this.mSensor = list.get(0); diff --git a/framework/src/org/apache/cordova/api/CordovaInterface.java b/framework/src/org/apache/cordova/api/CordovaInterface.java index ad5e065d..46b692b9 100755 --- a/framework/src/org/apache/cordova/api/CordovaInterface.java +++ b/framework/src/org/apache/cordova/api/CordovaInterface.java @@ -18,18 +18,8 @@ */ package org.apache.cordova.api; -import java.util.HashMap; - -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; +import android.app.Activity; import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; - /** * The Cordova activity abstract class that is extended by DroidGap. @@ -37,108 +27,53 @@ import android.net.Uri; */ public interface CordovaInterface { - /** - * @deprecated - * Add services to res/xml/plugins.xml instead. - * - * Add a class that implements a service. - * - * @param serviceType - * @param className - */ - @Deprecated - abstract public void addService(String serviceType, String className); - - /** - * Send JavaScript statement back to JavaScript. - * - * @param message - */ - abstract public void sendJavascript(String statement); - /** * 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 */ abstract public void startActivityForResult(IPlugin command, Intent intent, int requestCode); - /** - * Launch an activity for which you would not like a result when it finished. - * - * @param intent The intent to start - */ - abstract public void startActivity(Intent intent); - /** * Set the plugin to be called when a sub-activity exits. * - * @param plugin The plugin on which onActivityResult is to be called + * @param plugin The plugin on which onActivityResult is to be called */ abstract public void setActivityResultCallback(IPlugin plugin); /** - * Load the specified URL in the Cordova webview. + * Causes the Activity to override the back button behavior. * - * @param url The URL to load. + * @param override */ - abstract public void loadUrl(String url); + public abstract void bindBackButton(boolean override); /** - * Send a message to all plugins. + * A hook required to check if the Back Button is bound. + * + * @return + */ + public abstract boolean isBackButtonBound(); + + /** + * Get the Android activity. + * + * @return + */ + public abstract Activity getActivity(); + + @Deprecated + public abstract void cancelLoadUrl(); + + /** + * Called when a message is sent to plugin. * * @param id The message id * @param data The message data + * @return Object or null */ - abstract public void postMessage(String id, Object data); - - - public abstract Resources getResources(); - - public abstract String getPackageName(); - - public abstract Object getSystemService(String service); - - public abstract Context getContext(); - - public abstract Context getBaseContext(); - - public abstract Intent registerReceiver(BroadcastReceiver receiver, - IntentFilter intentFilter); - - public abstract ContentResolver getContentResolver(); - - public abstract void unregisterReceiver(BroadcastReceiver receiver); - - public abstract Cursor managedQuery(Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder); - - public abstract void runOnUiThread(Runnable runnable); - - public abstract AssetManager getAssets(); - - public abstract void clearCache(); - - public abstract void clearHistory(); - - public abstract boolean backHistory(); - - //public abstract void addWhiteListEntry(String origin, boolean subdomains); - - public abstract void bindBackButton(boolean override); - - public abstract boolean isBackButtonBound(); - - public abstract void cancelLoadUrl(); - - public abstract void showWebPage(String url, boolean openExternal, - boolean clearHistory, HashMap params); - - public abstract Context getApplicationContext(); - - public abstract boolean isUrlWhiteListed(String source); - + public Object onMessage(String id, Object data); } diff --git a/framework/src/org/apache/cordova/api/IPlugin.java b/framework/src/org/apache/cordova/api/IPlugin.java index c23cc3a7..870bb9e1 100755 --- a/framework/src/org/apache/cordova/api/IPlugin.java +++ b/framework/src/org/apache/cordova/api/IPlugin.java @@ -18,9 +18,11 @@ */ package org.apache.cordova.api; +import org.apache.cordova.CordovaWebView; import org.json.JSONArray; + +//import android.content.Context; import android.content.Intent; -import android.webkit.WebView; /** * Plugin interface must be implemented by any plugin classes. @@ -50,18 +52,18 @@ public interface IPlugin { /** * Sets the context of the Plugin. This can then be used to do things like * get file paths associated with the Activity. - * + * * @param ctx The context of the main Activity. */ void setContext(CordovaInterface ctx); /** - * Sets the main View of the application, this is the WebView within which + * Sets the main View of the application, this is the WebView within which * a Cordova app runs. - * + * * @param webView The Cordova WebView */ - void setView(WebView webView); + void setView(CordovaWebView webView); /** * Called when the system is about to start resuming a previous activity. @@ -92,8 +94,9 @@ public interface IPlugin { * * @param id The message id * @param data The message data + * @return Object to stop propagation or null */ - public void onMessage(String id, Object data); + public Object onMessage(String id, Object data); /** * Called when an activity you launched exits, giving you the requestCode you started it with, diff --git a/framework/src/org/apache/cordova/api/Plugin.java b/framework/src/org/apache/cordova/api/Plugin.java index 05c880f9..143655b1 100755 --- a/framework/src/org/apache/cordova/api/Plugin.java +++ b/framework/src/org/apache/cordova/api/Plugin.java @@ -18,11 +18,10 @@ */ package org.apache.cordova.api; +import org.apache.cordova.CordovaWebView; import org.json.JSONArray; import org.json.JSONObject; - import android.content.Intent; -import android.webkit.WebView; /** * Plugin interface must be implemented by any plugin classes. @@ -32,12 +31,12 @@ import android.webkit.WebView; public abstract class Plugin implements IPlugin { public String id; - public WebView webView; // WebView object - public CordovaInterface ctx; // CordovaActivity object + public CordovaWebView webView; // WebView object + public CordovaInterface ctx; // CordovaActivity object /** * 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. @@ -66,18 +65,18 @@ public abstract class Plugin implements IPlugin { } /** - * Sets the main View of the application, this is the WebView within which + * Sets the main View of the application, this is the WebView within which * a Cordova app runs. - * + * * @param webView The Cordova WebView */ - public void setView(WebView webView) { + public void setView(CordovaWebView webView) { this.webView = webView; } /** - * Called when the system is about to start resuming a previous activity. - * + * Called when the system is about to start resuming a previous activity. + * * @param multitasking Flag indicating if multitasking is turned on for app */ public void onPause(boolean multitasking) { @@ -108,8 +107,10 @@ public abstract class Plugin implements IPlugin { * * @param id The message id * @param data The message data + * @return Object to stop propagation or null */ - public void onMessage(String id, Object data) { + public Object onMessage(String id, Object data) { + return null; } /** @@ -141,7 +142,7 @@ public abstract class Plugin implements IPlugin { * @param statement */ public void sendJavascript(String statement) { - this.ctx.sendJavascript(statement); + this.webView.sendJavascript(statement); } /** @@ -155,7 +156,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void success(PluginResult pluginResult, String callbackId) { - this.ctx.sendJavascript(pluginResult.toSuccessCallbackString(callbackId)); + this.webView.sendJavascript(pluginResult.toSuccessCallbackString(callbackId)); } /** @@ -165,7 +166,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void success(JSONObject message, String callbackId) { - this.ctx.sendJavascript(new PluginResult(PluginResult.Status.OK, message).toSuccessCallbackString(callbackId)); + this.webView.sendJavascript(new PluginResult(PluginResult.Status.OK, message).toSuccessCallbackString(callbackId)); } /** @@ -175,7 +176,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void success(String message, String callbackId) { - this.ctx.sendJavascript(new PluginResult(PluginResult.Status.OK, message).toSuccessCallbackString(callbackId)); + this.webView.sendJavascript(new PluginResult(PluginResult.Status.OK, message).toSuccessCallbackString(callbackId)); } /** @@ -185,7 +186,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void error(PluginResult pluginResult, String callbackId) { - this.ctx.sendJavascript(pluginResult.toErrorCallbackString(callbackId)); + this.webView.sendJavascript(pluginResult.toErrorCallbackString(callbackId)); } /** @@ -195,7 +196,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void error(JSONObject message, String callbackId) { - this.ctx.sendJavascript(new PluginResult(PluginResult.Status.ERROR, message).toErrorCallbackString(callbackId)); + this.webView.sendJavascript(new PluginResult(PluginResult.Status.ERROR, message).toErrorCallbackString(callbackId)); } /** @@ -205,6 +206,6 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void error(String message, String callbackId) { - this.ctx.sendJavascript(new PluginResult(PluginResult.Status.ERROR, message).toErrorCallbackString(callbackId)); + this.webView.sendJavascript(new PluginResult(PluginResult.Status.ERROR, message).toErrorCallbackString(callbackId)); } } diff --git a/framework/src/org/apache/cordova/api/PluginEntry.java b/framework/src/org/apache/cordova/api/PluginEntry.java index 5cbba08d..857d4dd6 100755 --- a/framework/src/org/apache/cordova/api/PluginEntry.java +++ b/framework/src/org/apache/cordova/api/PluginEntry.java @@ -18,7 +18,10 @@ */ package org.apache.cordova.api; -import android.webkit.WebView; +import org.apache.cordova.CordovaWebView; + +//import android.content.Context; +//import android.webkit.WebView; /** * This class represents a service entry object. @@ -66,12 +69,12 @@ public class PluginEntry { * * @return The plugin object */ - @SuppressWarnings("unchecked") - public IPlugin createPlugin(WebView webView, CordovaInterface ctx) { + public IPlugin createPlugin(CordovaWebView webView, CordovaInterface ctx) { if (this.plugin != null) { return this.plugin; } try { + @SuppressWarnings("rawtypes") Class c = getClassByName(this.pluginClass); if (isCordovaPlugin(c)) { this.plugin = (IPlugin) c.newInstance(); @@ -93,7 +96,7 @@ public class PluginEntry { * @return * @throws ClassNotFoundException */ - @SuppressWarnings("unchecked") + @SuppressWarnings("rawtypes") private Class getClassByName(final String clazz) throws ClassNotFoundException { Class c = null; if (clazz != null) { @@ -109,7 +112,7 @@ public class PluginEntry { * @param c The class to check the interfaces of. * @return Boolean indicating if the class implements org.apache.cordova.api.Plugin */ - @SuppressWarnings("unchecked") + @SuppressWarnings("rawtypes") private boolean isCordovaPlugin(Class c) { if (c != null) { return org.apache.cordova.api.Plugin.class.isAssignableFrom(c) || org.apache.cordova.api.IPlugin.class.isAssignableFrom(c); diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java index 486ff29d..b9df52d7 100755 --- a/framework/src/org/apache/cordova/api/PluginManager.java +++ b/framework/src/org/apache/cordova/api/PluginManager.java @@ -23,13 +23,13 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; +import org.apache.cordova.CordovaWebView; import org.json.JSONArray; import org.json.JSONException; import org.xmlpull.v1.XmlPullParserException; import android.content.Intent; import android.content.res.XmlResourceParser; -import android.webkit.WebView; /** * PluginManager is exposed to JavaScript in the Cordova WebView. @@ -44,7 +44,7 @@ public class PluginManager { private final HashMap entries = new HashMap(); private final CordovaInterface ctx; - private final WebView app; + private final CordovaWebView app; // Flag to track first time through private boolean firstRun; @@ -59,7 +59,7 @@ public class PluginManager { * @param app * @param ctx */ - public PluginManager(WebView app, CordovaInterface ctx) { + public PluginManager(CordovaWebView app, CordovaInterface ctx) { this.ctx = ctx; this.app = app; this.firstRun = true; @@ -72,9 +72,9 @@ public class PluginManager { LOG.d(TAG, "init()"); // If first time, then load plugins from plugins.xml file - if (firstRun) { + if (this.firstRun) { this.loadPlugins(); - firstRun = false; + this.firstRun = false; } // Stop plugins on current HTML page and discard plugin objects @@ -92,11 +92,11 @@ public class PluginManager { * Load plugins from res/xml/plugins.xml */ public void loadPlugins() { - int id = ctx.getResources().getIdentifier("plugins", "xml", ctx.getPackageName()); + int id = this.ctx.getActivity().getResources().getIdentifier("plugins", "xml", this.ctx.getActivity().getPackageName()); if (id == 0) { - pluginConfigurationMissing(); + this.pluginConfigurationMissing(); } - XmlResourceParser xml = ctx.getResources().getXml(id); + XmlResourceParser xml = this.ctx.getActivity().getResources().getXml(id); int eventType = -1; String service = "", pluginClass = ""; boolean onload = false; @@ -167,14 +167,13 @@ public class PluginManager { * * @return JSON encoded string with a response message and status. */ - @SuppressWarnings("unchecked") public String exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async) { PluginResult cr = null; boolean runAsync = async; try { final JSONArray args = new JSONArray(jsonArgs); final IPlugin plugin = this.getPlugin(service); - final CordovaInterface ctx = this.ctx; + //final CordovaInterface ctx = this.ctx; if (plugin != null) { runAsync = async && !plugin.isSynch(action); if (runAsync) { @@ -192,16 +191,16 @@ public class PluginManager { // Check the success (OK, NO_RESULT & !KEEP_CALLBACK) else if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) { - ctx.sendJavascript(cr.toSuccessCallbackString(callbackId)); + app.sendJavascript(cr.toSuccessCallbackString(callbackId)); } // If error else { - ctx.sendJavascript(cr.toErrorCallbackString(callbackId)); + app.sendJavascript(cr.toErrorCallbackString(callbackId)); } } catch (Exception e) { PluginResult cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage()); - ctx.sendJavascript(cr.toErrorCallbackString(callbackId)); + app.sendJavascript(cr.toErrorCallbackString(callbackId)); } } }); @@ -226,7 +225,7 @@ public class PluginManager { if (cr == null) { cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION); } - ctx.sendJavascript(cr.toErrorCallbackString(callbackId)); + app.sendJavascript(cr.toErrorCallbackString(callbackId)); } return (cr != null ? cr.getJSONString() : "{ status: 0, message: 'all good' }"); } @@ -240,7 +239,7 @@ public class PluginManager { * @return IPlugin or null */ private IPlugin getPlugin(String service) { - PluginEntry entry = entries.get(service); + PluginEntry entry = this.entries.get(service); if (entry == null) { return null; } @@ -315,13 +314,22 @@ public class PluginManager { * * @param id The message id * @param data The message data + * @return */ - public void postMessage(String id, Object data) { + public Object postMessage(String id, Object data) { + Object obj = this.ctx.onMessage(id, data); + if (obj != null) { + return obj; + } for (PluginEntry entry : this.entries.values()) { if (entry.plugin != null) { - entry.plugin.onMessage(id, data); + obj = entry.plugin.onMessage(id, data); + if (obj != null) { + return obj; + } } } + return null; } /** diff --git a/framework/src/org/apache/cordova/api/PluginResult.java b/framework/src/org/apache/cordova/api/PluginResult.java index 8c1b7750..9ec24d9a 100755 --- a/framework/src/org/apache/cordova/api/PluginResult.java +++ b/framework/src/org/apache/cordova/api/PluginResult.java @@ -25,12 +25,13 @@ public class PluginResult { private final int status; private final String message; private boolean keepCallback = false; + public PluginResult(Status status) { this.status = status.ordinal(); this.message = "'" + PluginResult.StatusMessages[this.status] + "'"; } - + public PluginResult(Status status, String message) { this.status = status.ordinal(); this.message = JSONObject.quote(message); @@ -60,11 +61,11 @@ public class PluginResult { this.status = status.ordinal(); this.message = ""+b; } - + public void setKeepCallback(boolean b) { this.keepCallback = b; } - + public int getStatus() { return status; } @@ -72,23 +73,23 @@ public class PluginResult { public String getMessage() { return message; } - + public boolean getKeepCallback() { return this.keepCallback; } - + public String getJSONString() { return "{\"status\":" + this.status + ",\"message\":" + this.message + ",\"keepCallback\":" + this.keepCallback + "}"; } - + public String toSuccessCallbackString(String callbackId) { return "cordova.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");"; } - + public String toErrorCallbackString(String callbackId) { return "cordova.callbackError('"+callbackId+"', " + this.getJSONString()+ ");"; } - + public static String[] StatusMessages = new String[] { "No result", "OK", @@ -101,7 +102,7 @@ public class PluginResult { "JSON error", "Error" }; - + public enum Status { NO_RESULT, OK, diff --git a/guides/Cordova Upgrade Guide.md b/guides/Cordova Upgrade Guide.md new file mode 100644 index 00000000..c4dcd5ee --- /dev/null +++ b/guides/Cordova Upgrade Guide.md @@ -0,0 +1,29 @@ +# Cordova Upgrade Guide # + +This document is for people who need to upgrade their Cordova versions from an older version to a current version of Cordova. + +- To upgrade to 1.8.0, please go from 1.7.0 +- To upgrade from 1.7.0, please go from 1.6.0 + +## Upgrade to 1.8.0 from 1.7.0 ## + +1. Remove cordova-1.7.0.jar from the libs directory in your project +2. Add cordova-1.8.0.jar to the libs directory in your project +3. If you are using Eclipse, please refresh your eclipse project and do a clean +4. Copy the new cordova-1.7.0.js into your project +5. Update your HTML to sue the new cordova-1.7.0.js file +6. Update the res/xml/plugins.xml to be the same as the one found in framework/res/xml/plugins.xml + + +## Upgrade to 1.7.0 from 1.6.0 ## + +1. Remove cordova-1.6.0.jar from the libs directory in your project +2. Add cordova-1.6.0.jar to the libs directory in your project +3. If you are using Eclipse, please refresh your eclipse project and do a clean +4. Copy the new cordova-1.6.0.js into your project +5. Update your HTML to sue the new cordova-1.6.0.js file +6. Update the res/xml/plugins.xml to be the same as the one found in framework/res/xml/plugins.xml + + + + diff --git a/guides/CordovaWebView Guide.md b/guides/CordovaWebView Guide.md new file mode 100644 index 00000000..34bbb437 --- /dev/null +++ b/guides/CordovaWebView Guide.md @@ -0,0 +1,53 @@ +# How to use Cordova as a component # + +Beginning in Cordova 1.8, with the assistance of the CordovaActivity, you can use Cordova as a component in your Android applications. This component is known in Android +as the CordovaWebView, and new Cordova-based applications from 1.8 and greater will be using the CordovaWebView as its main view, whether the legacy DroidGap approach is +used or not. + + +The pre-requisites are the same as the pre-requisites for Android application development. It is assumed that you are familiar with Android Development. If not, please +look at the Getting Started guide to developing an Cordova Application and start there before continuing with this approach. Since this is not the main method that people use +to run applications, the instructions are currently manual. In the future, we may try to further automate the project generation. + +## Pre-requisites ## + +1. **Cordova 1.8** or greater downloaded +2. Android SDK updated with 15 + +## Guide to using CordovaWebView in an Android Project ## + +1. Use bin/create to fetch the commons-codec-1.6.jar +2. Go into framework and run ant jar to build the cordova jar (currently cordova-1.8.jar at the time of writing) +3. Copy the cordova jar into your Android project libs directory +4. Edit your main.xml to look similar the following. The layout_height, layout_width and ID can be modified to suit your application: + +` $ +` + +5. Modify your activity so that it implements the CordovaInterface. It is recommended that you implement the methods that are included. You may wish to copy the methods from framework/src/org/apache/cordova/DroidGap.java, or you may wish to implement your own methods. Below is a fragment of code from a basic application that uses the interface: + +` +public class CordovaViewTestActivity extends Activity implements CordovaInterface {$ + CordovaWebView phoneGap;$ + $ + /** Called when the activity is first created. */$ + @Override$ + public void onCreate(Bundle savedInstanceState) {$ + super.onCreate(savedInstanceState);$ + setContentView(R.layout.main);$ + $ + phoneGap = (CordovaWebView) findViewById(R.id.phoneGapView);$ + $ + phoneGap.loadUrl("file:///android_asset/www/index.html");$ + }$ +` + +6. Copy the HTML and Javascript used to the assets directory of your Android project +7. Copy cordova.xml and plugins.xml from framework/res/xml to the framework/res/xml in your project + + + + diff --git a/test/.classpath b/test/.classpath old mode 100755 new mode 100644 diff --git a/test/.project b/test/.project old mode 100755 new mode 100644 index d31f9d76..8f21f7f7 --- a/test/.project +++ b/test/.project @@ -1,6 +1,6 @@ - CordovaTest + PhoneGapViewTestActivity diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml index 21a8d8f4..06216ece 100755 --- a/test/AndroidManifest.xml +++ b/test/AndroidManifest.xml @@ -20,20 +20,20 @@ + android:largeScreens="true" + android:normalScreens="true" + android:smallScreens="true" + android:xlargeScreens="true" + android:resizeable="true" + android:anyDensity="true" + /> - + @@ -44,21 +44,66 @@ + + - - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:configChanges="orientation|keyboardHidden"> @@ -99,7 +144,14 @@ + + + + + + - - diff --git a/test/README.md b/test/README.md index be2df511..79cf85ff 100755 --- a/test/README.md +++ b/test/README.md @@ -1,23 +1,30 @@ -## Android Native Tests ## +# Android Native Tests # These tests are designed to verify Android native features and other Android specific features. +## Initial Setup ## + Before running the tests, they need to be set up. -1. Copy the version of cordova-x.y.z.js into assets/www directory -2. Edit assets/www/cordova.js to reference the correct version -3. Copy cordova-x.y.z.jar into libs directory +0. Copy cordova-x.y.z.jar into libs directory To run from command line: -4. Build by entering "ant debug install" -5. Run tests by clicking on "CordovaTest" icon on device +0. Build by entering `ant debug install` +0. Run tests by clicking on "CordovaTest" icon on device To run from Eclipse: -4. Import Android project into Eclipse -5. Ensure Project properties "Java Build Path" includes the lib/cordova-x.y.z.jar -6. Create run configuration if not already created -7. Run tests +0. Import Android project into Eclipse +0. Ensure Project properties "Java Build Path" includes the lib/cordova-x.y.z.jar +0. Create run configuration if not already created +0. Run tests +## Automatic Runs ## +Once you have installed the test, you can launch and run the tests +automatically with the below command: + + adb shell am instrument -w org.apache.cordova.test/android.test.InstrumentationTestRunner + +(Optionally, you can also run in Eclipse) diff --git a/test/assets/www/backgroundcolor/index.html b/test/assets/www/backgroundcolor/index.html new file mode 100755 index 00000000..0746dcf6 --- /dev/null +++ b/test/assets/www/backgroundcolor/index.html @@ -0,0 +1,41 @@ + + + + + + + Cordova Tests + + + + + +

Background Color Test

+
+

Platform:  , Version:  

+

UUID:  , Name:  

+

Width:  , Height:   + , Color Depth:

+
+
+ Before this page was show, you should have seen the background flash green.
+
+ + diff --git a/test/assets/www/basicauth/index.html b/test/assets/www/basicauth/index.html new file mode 100755 index 00000000..02ff0b2a --- /dev/null +++ b/test/assets/www/basicauth/index.html @@ -0,0 +1,42 @@ + + + + + + + Cordova Tests + + + + + +

Basic Auth

+
+

Platform:  , Version:  

+

UUID:  , Name:  

+

Width:  , Height:   + , Color Depth:

+
+
+ Loading link below should be successful and show page indicating username=test & password=test.
+
+ Test password + + diff --git a/test/assets/www/cordova-1.6.0.js b/test/assets/www/cordova-1.6.0.js deleted file mode 100755 index a63913fc..00000000 --- a/test/assets/www/cordova-1.6.0.js +++ /dev/null @@ -1,4985 +0,0 @@ -// File generated at :: Tue Apr 10 2012 08:34:16 GMT-0500 (CDT) - -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. -*/ - -;(function() { - -// file: lib/scripts/require.js -var require, - define; - -(function () { - var modules = {}; - - function build(module) { - var factory = module.factory; - module.exports = {}; - delete module.factory; - factory(require, module.exports, module); - return module.exports; - } - - require = function (id) { - if (!modules[id]) { - throw "module " + id + " not found"; - } - return modules[id].factory ? build(modules[id]) : modules[id].exports; - }; - - define = function (id, factory) { - if (modules[id]) { - throw "module " + id + " already defined"; - } - - modules[id] = { - id: id, - factory: factory - }; - }; - - define.remove = function (id) { - delete modules[id]; - }; - -})(); - -//Export for use in node -if (typeof module === "object" && typeof require === "function") { - module.exports.require = require; - module.exports.define = define; -} - -// file: lib/cordova.js -define("cordova", function(require, exports, module) { -var channel = require('cordova/channel'); -/** - * Intercept calls to addEventListener + removeEventListener and handle deviceready, - * resume, and pause events. - */ -var m_document_addEventListener = document.addEventListener; -var m_document_removeEventListener = document.removeEventListener; -var m_window_addEventListener = window.addEventListener; -var m_window_removeEventListener = window.removeEventListener; - -/** - * Houses custom event handlers to intercept on document + window event listeners. - */ -var documentEventHandlers = {}, - windowEventHandlers = {}; - -document.addEventListener = function(evt, handler, capture) { - var e = evt.toLowerCase(); - if (e == 'deviceready') { - channel.onDeviceReady.subscribeOnce(handler); - } else if (e == 'resume') { - channel.onResume.subscribe(handler); - // if subscribing listener after event has already fired, invoke the handler - if (channel.onResume.fired && handler instanceof Function) { - handler(); - } - } else if (e == 'pause') { - channel.onPause.subscribe(handler); - } else if (typeof documentEventHandlers[e] != 'undefined') { - documentEventHandlers[e].subscribe(handler); - } else { - m_document_addEventListener.call(document, evt, handler, capture); - } -}; - -window.addEventListener = function(evt, handler, capture) { - var e = evt.toLowerCase(); - if (typeof windowEventHandlers[e] != 'undefined') { - windowEventHandlers[e].subscribe(handler); - } else { - m_window_addEventListener.call(window, evt, handler, capture); - } -}; - -document.removeEventListener = function(evt, handler, capture) { - var e = evt.toLowerCase(); - // If unsubcribing from an event that is handled by a plugin - if (typeof documentEventHandlers[e] != "undefined") { - documentEventHandlers[e].unsubscribe(handler); - } else { - m_document_removeEventListener.call(document, evt, handler, capture); - } -}; - -window.removeEventListener = function(evt, handler, capture) { - var e = evt.toLowerCase(); - // If unsubcribing from an event that is handled by a plugin - if (typeof windowEventHandlers[e] != "undefined") { - windowEventHandlers[e].unsubscribe(handler); - } else { - m_window_removeEventListener.call(window, evt, handler, capture); - } -}; - -function createEvent(type, data) { - var event = document.createEvent('Events'); - event.initEvent(type, false, false); - if (data) { - for (var i in data) { - if (data.hasOwnProperty(i)) { - event[i] = data[i]; - } - } - } - return event; -} - -if(typeof window.console === "undefined") -{ - window.console = { - log:function(){} - }; -} - -var cordova = { - define:define, - require:require, - /** - * Methods to add/remove your own addEventListener hijacking on document + window. - */ - addWindowEventHandler:function(event, opts) { - return (windowEventHandlers[event] = channel.create(event, opts)); - }, - addDocumentEventHandler:function(event, opts) { - return (documentEventHandlers[event] = channel.create(event, opts)); - }, - removeWindowEventHandler:function(event) { - delete windowEventHandlers[event]; - }, - removeDocumentEventHandler:function(event) { - delete documentEventHandlers[event]; - }, - /** - * Retreive original event handlers that were replaced by Cordova - * - * @return object - */ - getOriginalHandlers: function() { - return {'document': {'addEventListener': m_document_addEventListener, 'removeEventListener': m_document_removeEventListener}, - 'window': {'addEventListener': m_window_addEventListener, 'removeEventListener': m_window_removeEventListener}}; - }, - /** - * Method to fire event from native code - */ - fireDocumentEvent: function(type, data) { - var evt = createEvent(type, data); - if (typeof documentEventHandlers[type] != 'undefined') { - documentEventHandlers[type].fire(evt); - } else { - document.dispatchEvent(evt); - } - }, - fireWindowEvent: function(type, data) { - var evt = createEvent(type,data); - if (typeof windowEventHandlers[type] != 'undefined') { - windowEventHandlers[type].fire(evt); - } else { - window.dispatchEvent(evt); - } - }, - // TODO: this is Android only; think about how to do this better - shuttingDown:false, - UsePolling:false, - // END TODO - - // TODO: iOS only - // This queue holds the currently executing command and all pending - // commands executed with cordova.exec(). - commandQueue:[], - // Indicates if we're currently in the middle of flushing the command - // queue on the native side. - commandQueueFlushing:false, - // END TODO - /** - * Plugin callback mechanism. - */ - callbackId: 0, - callbacks: {}, - 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 - }, - - /** - * Called by native code when returning successful result from an action. - * - * @param callbackId - * @param args - */ - callbackSuccess: function(callbackId, args) { - if (cordova.callbacks[callbackId]) { - - // If result is to be sent to callback - if (args.status == cordova.callbackStatus.OK) { - try { - if (cordova.callbacks[callbackId].success) { - cordova.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 cordova.callbacks[callbackId]; - } - } - }, - - /** - * Called by native code when returning error result from an action. - * - * @param callbackId - * @param args - */ - callbackError: function(callbackId, args) { - if (cordova.callbacks[callbackId]) { - try { - if (cordova.callbacks[callbackId].fail) { - cordova.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 cordova.callbacks[callbackId]; - } - } - }, - // TODO: remove in 2.0. - addPlugin: function(name, obj) { - console.log("[DEPRECATION NOTICE] window.addPlugin and window.plugins will be removed in version 2.0."); - if (!window.plugins[name]) { - window.plugins[name] = obj; - } - else { - console.log("Error: Plugin "+name+" already exists."); - } - }, - - addConstructor: function(func) { - channel.onCordovaReady.subscribeOnce(function() { - try { - func(); - } catch(e) { - console.log("Failed to run constructor: " + e); - } - }); - } -}; - -/** - * Legacy variable for plugin support - * TODO: remove in 2.0. - */ -if (!window.PhoneGap) { - window.PhoneGap = cordova; -} - -/** - * Plugins object - * TODO: remove in 2.0. - */ -if (!window.plugins) { - window.plugins = {}; -} - -module.exports = cordova; - -}); - -// file: lib/common/builder.js -define("cordova/builder", function(require, exports, module) { -function each(objects, func, context) { - for (var prop in objects) { - if (objects.hasOwnProperty(prop)) { - func.apply(context, [objects[prop], prop]); - } - } -} - -function include(parent, objects, clobber, merge) { - each(objects, function (obj, key) { - try { - var result = obj.path ? require(obj.path) : {}; - - if (clobber) { - // Clobber if it doesn't exist. - if (typeof parent[key] === 'undefined') { - parent[key] = result; - } else if (typeof obj.path !== 'undefined') { - // If merging, merge properties onto parent, otherwise, clobber. - if (merge) { - recursiveMerge(parent[key], result); - } else { - parent[key] = result; - } - } - result = parent[key]; - } else { - // Overwrite if not currently defined. - if (typeof parent[key] == 'undefined') { - parent[key] = result; - } else if (merge && typeof obj.path !== 'undefined') { - // If merging, merge parent onto result - recursiveMerge(result, parent[key]); - parent[key] = result; - } else { - // Set result to what already exists, so we can build children into it if they exist. - result = parent[key]; - } - } - - if (obj.children) { - include(result, obj.children, clobber, merge); - } - } catch(e) { - utils.alert('Exception building cordova JS globals: ' + e + ' for key "' + key + '"'); - } - }); -} - -/** - * Merge properties from one object onto another recursively. Properties from - * the src object will overwrite existing target property. - * - * @param target Object to merge properties into. - * @param src Object to merge properties from. - */ -function recursiveMerge(target, src) { - for (var prop in src) { - if (src.hasOwnProperty(prop)) { - if (typeof target.prototype !== 'undefined' && target.prototype.constructor === target) { - // If the target object is a constructor override off prototype. - target.prototype[prop] = src[prop]; - } else { - target[prop] = typeof src[prop] === 'object' ? recursiveMerge( - target[prop], src[prop]) : src[prop]; - } - } - } - return target; -} - -module.exports = { - build: function (objects) { - return { - intoButDontClobber: function (target) { - include(target, objects, false, false); - }, - intoAndClobber: function(target) { - include(target, objects, true, false); - }, - intoAndMerge: function(target) { - include(target, objects, true, true); - } - }; - } -}; - -}); - -// file: lib/common/channel.js -define("cordova/channel", function(require, exports, module) { -/** - * Custom pub-sub "channel" that can have functions subscribed to it - * This object is used to define and control firing of events for - * cordova initialization. - * - * The order of events during page load and Cordova startup is as follows: - * - * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. - * onNativeReady Internal event that indicates the Cordova native side is ready. - * onCordovaReady Internal event fired when all Cordova JavaScript objects have been created. - * onCordovaInfoReady Internal event fired when device properties are available. - * onCordovaConnectionReady Internal event fired when the connection property has been set. - * onDeviceReady User event fired to indicate that Cordova is ready - * onResume User event fired to indicate a start/resume lifecycle event - * onPause User event fired to indicate a pause lifecycle event - * onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one). - * - * The only Cordova events that user code should register for are: - * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript - * pause App has moved to background - * resume App has returned to foreground - * - * Listeners can be registered as: - * document.addEventListener("deviceready", myDeviceReadyListener, false); - * document.addEventListener("resume", myResumeListener, false); - * document.addEventListener("pause", myPauseListener, false); - * - * The DOM lifecycle events should be used for saving and restoring state - * window.onload - * window.onunload - * - */ - -/** - * Channel - * @constructor - * @param type String the channel name - * @param opts Object options to pass into the channel, currently - * supports: - * onSubscribe: callback that fires when - * something subscribes to the Channel. Sets - * context to the Channel. - * onUnsubscribe: callback that fires when - * something unsubscribes to the Channel. Sets - * context to the Channel. - */ -var Channel = function(type, opts) { - this.type = type; - this.handlers = {}; - this.numHandlers = 0; - this.guid = 0; - this.fired = false; - this.enabled = true; - this.events = { - onSubscribe:null, - onUnsubscribe:null - }; - if (opts) { - if (opts.onSubscribe) this.events.onSubscribe = opts.onSubscribe; - if (opts.onUnsubscribe) this.events.onUnsubscribe = opts.onUnsubscribe; - } - }, - channel = { - /** - * Calls the provided function only after all of the channels specified - * have been fired. - */ - join: function (h, c) { - var i = c.length; - var len = i; - var f = function() { - if (!(--i)) h(); - }; - for (var j=0; j 0) { - eval("var v="+r+";"); - - // If status is OK, then return value back to caller - if (v.status === cordova.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 cordova.callbacks[callbackId]; - } - } - return v.message; - } - - // If no result - else if (v.status === cordova.callbackStatus.NO_RESULT) { - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - - // If error, then display error - else { - console.log("Error: Status="+v.status+" Message="+v.message); - - // If there is a fail callback, then call it now with returned value - if (fail) { - try { - fail(v.message); - } - catch (e1) { - console.log("Error in error callback: "+callbackId+" = "+e1); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - return null; - } - } - } catch (e2) { - console.log("Error: "+e2); - } -}; - -}); - -// file: lib/android/platform.js -define("cordova/platform", function(require, exports, module) { -module.exports = { - id: "android", - initialize:function() { - var channel = require("cordova/channel"), - cordova = require('cordova'), - callback = require('cordova/plugin/android/callback'), - polling = require('cordova/plugin/android/polling'), - exec = require('cordova/exec'); - - channel.onDestroy.subscribe(function() { - cordova.shuttingDown = true; - }); - - // Start listening for XHR callbacks - // Figure out which bridge approach will work on this Android - // device: polling or XHR-based callbacks - setTimeout(function() { - if (cordova.UsePolling) { - polling(); - } - else { - var isPolling = prompt("usePolling", "gap_callbackServer:"); - cordova.UsePolling = isPolling; - if (isPolling == "true") { - cordova.UsePolling = true; - polling(); - } else { - cordova.UsePolling = false; - callback(); - } - } - }, 1); - - // Inject a listener for the backbutton on the document. - var backButtonChannel = cordova.addDocumentEventHandler('backbutton', { - onSubscribe:function() { - // If we just attached the first handler, let native know we need to override the back button. - if (this.numHandlers === 1) { - exec(null, null, "App", "overrideBackbutton", [true]); - } - }, - onUnsubscribe:function() { - // If we just detached the last handler, let native know we no longer override the back button. - if (this.numHandlers === 0) { - exec(null, null, "App", "overrideBackbutton", [false]); - } - } - }); - - // Add hardware MENU and SEARCH button handlers - cordova.addDocumentEventHandler('menubutton'); - cordova.addDocumentEventHandler('searchbutton'); - - // Figure out if we need to shim-in localStorage and WebSQL - // support from the native side. - var storage = require('cordova/plugin/android/storage'); - - // First patch WebSQL if necessary - if (typeof window.openDatabase == 'undefined') { - // Not defined, create an openDatabase function for all to use! - window.openDatabase = storage.openDatabase; - } else { - // Defined, but some Android devices will throw a SECURITY_ERR - - // so we wrap the whole thing in a try-catch and shim in our own - // if the device has Android bug 16175. - var originalOpenDatabase = window.openDatabase; - window.openDatabase = function(name, version, desc, size) { - var db = null; - try { - db = originalOpenDatabase(name, version, desc, size); - } - catch (ex) { - db = null; - } - - if (db === null) { - return storage.openDatabase(name, version, desc, size); - } - else { - return db; - } - - }; - } - - // Patch localStorage if necessary - if (typeof window.localStorage == 'undefined' || window.localStorage === null) { - window.localStorage = new storage.CupCakeLocalStorage(); - } - - // Let native code know we are all done on the JS side. - // Native code will then un-hide the WebView. - channel.join(function() { - prompt("", "gap_init:"); - }, [channel.onCordovaReady]); - }, - objects: { - cordova: { - children: { - JSCallback:{ - path:"cordova/plugin/android/callback" - }, - JSCallbackPolling:{ - path:"cordova/plugin/android/polling" - } - } - }, - navigator: { - children: { - app:{ - path: "cordova/plugin/android/app" - } - } - }, - device:{ - path: "cordova/plugin/android/device" - }, - File: { // exists natively on Android WebView, override - path: "cordova/plugin/File" - }, - FileReader: { // exists natively on Android WebView, override - path: "cordova/plugin/FileReader" - }, - FileError: { //exists natively on Android WebView on Android 4.x - path: "cordova/plugin/FileError" - }, - MediaError: { // exists natively on Android WebView on Android 4.x - path: "cordova/plugin/MediaError" - } - }, - merges: { - navigator: { - children: { - notification: { - path: 'cordova/plugin/android/notification' - } - } - } - } -}; - -}); - -// file: lib/common/plugin/Acceleration.js -define("cordova/plugin/Acceleration", function(require, exports, module) { -var Acceleration = function(x, y, z, timestamp) { - this.x = x; - this.y = y; - this.z = z; - this.timestamp = timestamp || (new Date()).getTime(); -}; - -module.exports = Acceleration; - -}); - -// file: lib/common/plugin/Camera.js -define("cordova/plugin/Camera", function(require, exports, module) { -var exec = require('cordova/exec'), - Camera = require('cordova/plugin/CameraConstants'); - -var cameraExport = {}; - -// Tack on the Camera Constants to the base camera plugin. -for (var key in Camera) { - cameraExport[key] = Camera[key]; -} - -/** - * 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=FILE_URL. - * - * @param {Function} successCallback - * @param {Function} errorCallback - * @param {Object} options - */ -cameraExport.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; - } - - var quality = 50; - if (options && typeof options.quality == "number") { - quality = options.quality; - } else if (options && typeof options.quality == "string") { - var qlity = parseInt(options.quality, 10); - if (isNaN(qlity) === false) { - quality = qlity.valueOf(); - } - } - - var destinationType = Camera.DestinationType.FILE_URI; - if (typeof options.destinationType == "number") { - destinationType = options.destinationType; - } - - var sourceType = Camera.PictureSourceType.CAMERA; - if (typeof options.sourceType == "number") { - sourceType = options.sourceType; - } - - var targetWidth = -1; - if (typeof options.targetWidth == "number") { - targetWidth = options.targetWidth; - } else if (typeof options.targetWidth == "string") { - var width = parseInt(options.targetWidth, 10); - if (isNaN(width) === false) { - targetWidth = width.valueOf(); - } - } - - var targetHeight = -1; - if (typeof options.targetHeight == "number") { - targetHeight = options.targetHeight; - } else if (typeof options.targetHeight == "string") { - var height = parseInt(options.targetHeight, 10); - if (isNaN(height) === false) { - targetHeight = height.valueOf(); - } - } - - var encodingType = Camera.EncodingType.JPEG; - if (typeof options.encodingType == "number") { - encodingType = options.encodingType; - } - - var mediaType = Camera.MediaType.PICTURE; - if (typeof options.mediaType == "number") { - mediaType = options.mediaType; - } - var allowEdit = false; - if (typeof options.allowEdit == "boolean") { - allowEdit = options.allowEdit; - } else if (typeof options.allowEdit == "number") { - allowEdit = options.allowEdit <= 0 ? false : true; - } - var correctOrientation = false; - if (typeof options.correctOrientation == "boolean") { - correctOrientation = options.correctOrientation; - } else if (typeof options.correctOrientation == "number") { - correctOrientation = options.correctOrientation <=0 ? false : true; - } - var saveToPhotoAlbum = false; - if (typeof options.saveToPhotoAlbum == "boolean") { - saveToPhotoAlbum = options.saveToPhotoAlbum; - } else if (typeof options.saveToPhotoAlbum == "number") { - saveToPhotoAlbum = options.saveToPhotoAlbum <=0 ? false : true; - } - - exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, mediaType, allowEdit, correctOrientation, saveToPhotoAlbum]); -} - -module.exports = cameraExport; - -}); - -// file: lib/common/plugin/CameraConstants.js -define("cordova/plugin/CameraConstants", function(require, exports, module) { -module.exports = { - DestinationType:{ - DATA_URL: 0, // Return base64 encoded string - FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) - }, - EncodingType:{ - JPEG: 0, // Return JPEG encoded image - PNG: 1 // Return PNG encoded image - }, - MediaType:{ - PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType - VIDEO: 1, // allow selection of video only, ONLY RETURNS URL - ALLMEDIA : 2 // allow selection from all media types - }, - 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) - } -}; - -}); - -// file: lib/common/plugin/CaptureAudioOptions.js -define("cordova/plugin/CaptureAudioOptions", function(require, exports, module) { -/** - * Encapsulates all audio capture operation configuration options. - */ -var CaptureAudioOptions = function(){ - // Upper limit of sound clips user can record. Value must be equal or greater than 1. - this.limit = 1; - // Maximum duration of a single sound clip in seconds. - this.duration = 0; - // The selected audio mode. Must match with one of the elements in supportedAudioModes array. - this.mode = null; -}; - -module.exports = CaptureAudioOptions; - -}); - -// file: lib/common/plugin/CaptureError.js -define("cordova/plugin/CaptureError", function(require, exports, module) { -/** - * The CaptureError interface encapsulates all errors in the Capture API. - */ -var CaptureError = function(c) { - this.code = c || null; -}; - -// Camera or microphone failed to capture image or sound. -CaptureError.CAPTURE_INTERNAL_ERR = 0; -// Camera application or audio capture application is currently serving other capture request. -CaptureError.CAPTURE_APPLICATION_BUSY = 1; -// Invalid use of the API (e.g. limit parameter has value less than one). -CaptureError.CAPTURE_INVALID_ARGUMENT = 2; -// User exited camera application or audio capture application before capturing anything. -CaptureError.CAPTURE_NO_MEDIA_FILES = 3; -// The requested capture operation is not supported. -CaptureError.CAPTURE_NOT_SUPPORTED = 20; - -module.exports = CaptureError; - -}); - -// file: lib/common/plugin/CaptureImageOptions.js -define("cordova/plugin/CaptureImageOptions", function(require, exports, module) { -/** - * Encapsulates all image capture operation configuration options. - */ -var CaptureImageOptions = function(){ - // Upper limit of images user can take. Value must be equal or greater than 1. - this.limit = 1; - // The selected image mode. Must match with one of the elements in supportedImageModes array. - this.mode = null; -}; - -module.exports = CaptureImageOptions; - -}); - -// file: lib/common/plugin/CaptureVideoOptions.js -define("cordova/plugin/CaptureVideoOptions", function(require, exports, module) { -/** - * Encapsulates all video capture operation configuration options. - */ -var CaptureVideoOptions = function(){ - // Upper limit of videos user can record. Value must be equal or greater than 1. - this.limit = 1; - // Maximum duration of a single video clip in seconds. - this.duration = 0; - // The selected video mode. Must match with one of the elements in supportedVideoModes array. - this.mode = null; -}; - -module.exports = CaptureVideoOptions; - -}); - -// file: lib/common/plugin/CompassError.js -define("cordova/plugin/CompassError", function(require, exports, module) { -/** - * CompassError. - * An error code assigned by an implementation when an error has occured - * @constructor - */ -var CompassError = function(err) { - this.code = (err !== undefined ? err : null); -}; - -CompassError.COMPASS_INTERNAL_ERR = 0; -CompassError.COMPASS_NOT_SUPPORTED = 20; - -module.exports = CompassError; - -}); - -// file: lib/common/plugin/CompassHeading.js -define("cordova/plugin/CompassHeading", function(require, exports, module) { -var CompassHeading = function(magneticHeading, trueHeading, headingAccuracy, timestamp) { - this.magneticHeading = (magneticHeading !== undefined ? magneticHeading : null); - this.trueHeading = (trueHeading !== undefined ? trueHeading : null); - this.headingAccuracy = (headingAccuracy !== undefined ? headingAccuracy : null); - this.timestamp = (timestamp !== undefined ? timestamp : new Date().getTime()); -}; - -module.exports = CompassHeading; - -}); - -// file: lib/common/plugin/ConfigurationData.js -define("cordova/plugin/ConfigurationData", function(require, exports, module) { -/** - * Encapsulates a set of parameters that the capture device supports. - */ -function ConfigurationData() { - // The ASCII-encoded string in lower case representing the media type. - this.type = null; - // The height attribute represents height of the image or video in pixels. - // In the case of a sound clip this attribute has value 0. - this.height = 0; - // The width attribute represents width of the image or video in pixels. - // In the case of a sound clip this attribute has value 0 - this.width = 0; -} - -module.exports = ConfigurationData; - -}); - -// file: lib/common/plugin/Connection.js -define("cordova/plugin/Connection", function(require, exports, module) { -/** - * Network status - */ -module.exports = { - UNKNOWN: "unknown", - ETHERNET: "ethernet", - WIFI: "wifi", - CELL_2G: "2g", - CELL_3G: "3g", - CELL_4G: "4g", - NONE: "none" -}; - -}); - -// file: lib/common/plugin/Contact.js -define("cordova/plugin/Contact", function(require, exports, module) { -var exec = require('cordova/exec'), - ContactError = require('cordova/plugin/ContactError'), - utils = require('cordova/utils'); - -/** -* Converts primitives into Complex Object -* Currently only used for Date fields -*/ -function convertIn(contact) { - var value = contact.birthday; - try { - contact.birthday = new Date(parseFloat(value)); - } catch (exception){ - console.log("Cordova Contact convertIn error: exception creating date."); - } - return contact; -}; - -/** -* Converts Complex objects into primitives -* Only conversion at present is for Dates. -**/ - -function convertOut(contact) { - var value = contact.birthday; - if (value != null) { - // try to make it a Date object if it is not already - if (!value instanceof Date){ - try { - value = new Date(value); - } catch(exception){ - value = null; - } - } - if (value instanceof Date){ - value = value.valueOf(); // convert to milliseconds - } - contact.birthday = value; - } - return contact; -}; - -/** -* Contains information about a single contact. -* @constructor -* @param {DOMString} id unique identifier -* @param {DOMString} displayName -* @param {ContactName} name -* @param {DOMString} nickname -* @param {Array.} phoneNumbers array of phone numbers -* @param {Array.} emails array of email addresses -* @param {Array.} addresses array of addresses -* @param {Array.} ims instant messaging user ids -* @param {Array.} organizations -* @param {DOMString} birthday contact's birthday -* @param {DOMString} note user notes about contact -* @param {Array.} photos -* @param {Array.} categories -* @param {Array.} urls contact's web sites -*/ -var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, - ims, organizations, birthday, note, photos, categories, urls) { - this.id = id || null; - this.rawId = 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.birthday = birthday || null; - this.note = note || null; - this.photos = photos || null; // ContactField[] - this.categories = categories || null; // ContactField[] - this.urls = urls || null; // ContactField[] -}; - -/** -* Removes contact from device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.remove = function(successCB, errorCB) { - var fail = function(code) { - errorCB(new ContactError(code)); - }; - if (this.id === null) { - fail(ContactError.UNKNOWN_ERROR); - } - else { - exec(successCB, fail, "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 = utils.clone(this); - var i; - clonedContact.id = null; - clonedContact.rawId = null; - // Loop through and clear out any id's in phones, emails, etc. - if (clonedContact.phoneNumbers) { - for (i = 0; i < clonedContact.phoneNumbers.length; i++) { - clonedContact.phoneNumbers[i].id = null; - } - } - if (clonedContact.emails) { - for (i = 0; i < clonedContact.emails.length; i++) { - clonedContact.emails[i].id = null; - } - } - if (clonedContact.addresses) { - for (i = 0; i < clonedContact.addresses.length; i++) { - clonedContact.addresses[i].id = null; - } - } - if (clonedContact.ims) { - for (i = 0; i < clonedContact.ims.length; i++) { - clonedContact.ims[i].id = null; - } - } - if (clonedContact.organizations) { - for (i = 0; i < clonedContact.organizations.length; i++) { - clonedContact.organizations[i].id = null; - } - } - if (clonedContact.categories) { - for (i = 0; i < clonedContact.categories.length; i++) { - clonedContact.categories[i].id = null; - } - } - if (clonedContact.photos) { - for (i = 0; i < clonedContact.photos.length; i++) { - clonedContact.photos[i].id = null; - } - } - if (clonedContact.urls) { - for (i = 0; i < clonedContact.urls.length; i++) { - clonedContact.urls[i].id = null; - } - } - return clonedContact; -}; - -/** -* Persists contact to device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.save = function(successCB, errorCB) { - var fail = function(code) { - errorCB(new ContactError(code)); - }; - var success = function(result) { - if (result) { - if (typeof successCB === 'function') { - var fullContact = require('cordova/plugin/contacts').create(result); - successCB(convertIn(fullContact)); - } - } - else { - // no Entry object returned - fail(ContactError.UNKNOWN_ERROR); - } - }; - var dupContact = convertOut(utils.clone(this)); - exec(success, fail, "Contacts", "save", [dupContact]); -}; - - -module.exports = Contact; - -}); - -// file: lib/common/plugin/ContactAddress.js -define("cordova/plugin/ContactAddress", function(require, exports, module) { -/** -* Contact address. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code -* @param formatted // NOTE: not a W3C standard -* @param streetAddress -* @param locality -* @param region -* @param postalCode -* @param country -*/ - -var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { - this.id = null; - this.pref = (typeof pref != 'undefined' ? pref : false); - this.type = type || null; - this.formatted = formatted || null; - this.streetAddress = streetAddress || null; - this.locality = locality || null; - this.region = region || null; - this.postalCode = postalCode || null; - this.country = country || null; -}; - -module.exports = ContactAddress; - -}); - -// file: lib/common/plugin/ContactError.js -define("cordova/plugin/ContactError", function(require, exports, module) { -/** - * ContactError. - * An error code assigned by an implementation when an error has occured - * @constructor - */ -var ContactError = function(err) { - this.code = (typeof err != 'undefined' ? err : null); -}; - -/** - * Error codes - */ -ContactError.UNKNOWN_ERROR = 0; -ContactError.INVALID_ARGUMENT_ERROR = 1; -ContactError.TIMEOUT_ERROR = 2; -ContactError.PENDING_OPERATION_ERROR = 3; -ContactError.IO_ERROR = 4; -ContactError.NOT_SUPPORTED_ERROR = 5; -ContactError.PERMISSION_DENIED_ERROR = 20; - -module.exports = ContactError; - -}); - -// file: lib/common/plugin/ContactField.js -define("cordova/plugin/ContactField", function(require, exports, module) { -/** -* Generic contact field. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard -* @param type -* @param value -* @param pref -*/ -var ContactField = function(type, value, pref) { - this.id = null; - this.type = type || null; - this.value = value || null; - this.pref = (typeof pref != 'undefined' ? pref : false); -}; - -module.exports = ContactField; - -}); - -// file: lib/common/plugin/ContactFindOptions.js -define("cordova/plugin/ContactFindOptions", function(require, exports, module) { -/** - * ContactFindOptions. - * @constructor - * @param filter used to match contacts against - * @param multiple boolean used to determine if more than one contact should be returned - */ - -var ContactFindOptions = function(filter, multiple) { - this.filter = filter || ''; - this.multiple = (typeof multiple != 'undefined' ? multiple : false); -}; - -module.exports = ContactFindOptions; - -}); - -// file: lib/common/plugin/ContactName.js -define("cordova/plugin/ContactName", function(require, exports, module) { -/** -* Contact name. -* @constructor -* @param formatted // NOTE: not part of W3C standard -* @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; -}; - -module.exports = ContactName; - -}); - -// file: lib/common/plugin/ContactOrganization.js -define("cordova/plugin/ContactOrganization", function(require, exports, module) { -/** -* Contact organization. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard -* @param name -* @param dept -* @param title -* @param startDate -* @param endDate -* @param location -* @param desc -*/ - -var ContactOrganization = function(pref, type, name, dept, title) { - this.id = null; - this.pref = (typeof pref != 'undefined' ? pref : false); - this.type = type || null; - this.name = name || null; - this.department = dept || null; - this.title = title || null; -}; - -module.exports = ContactOrganization; - -}); - -// file: lib/common/plugin/Coordinates.js -define("cordova/plugin/Coordinates", function(require, exports, module) { -/** - * This class contains position information. - * @param {Object} lat - * @param {Object} lng - * @param {Object} alt - * @param {Object} acc - * @param {Object} head - * @param {Object} vel - * @param {Object} altacc - * @constructor - */ -var Coordinates = function(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; -}; - -module.exports = Coordinates; - -}); - -// file: lib/common/plugin/DirectoryEntry.js -define("cordova/plugin/DirectoryEntry", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - Entry = require('cordova/plugin/Entry'), - DirectoryReader = require('cordova/plugin/DirectoryReader'); - -/** - * An interface representing a directory on the file system. - * - * {boolean} isFile always false (readonly) - * {boolean} isDirectory always true (readonly) - * {DOMString} name of the directory, excluding the path leading to it (readonly) - * {DOMString} fullPath the absolute full path to the directory (readonly) - * {FileSystem} filesystem on which the directory resides (readonly) - */ -var DirectoryEntry = function(name, fullPath) { - DirectoryEntry.__super__.constructor.apply(this, [false, true, name, fullPath]); -}; - -utils.extend(DirectoryEntry, Entry); - -/** - * Creates a new DirectoryReader to read entries from this directory - */ -DirectoryEntry.prototype.createReader = function() { - return new DirectoryReader(this.fullPath); -}; - -/** - * Creates or looks up a directory - * - * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory - * @param {Flags} options to create or excluively create the directory - * @param {Function} successCallback is called with the new entry - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var entry = new DirectoryEntry(result.name, result.fullPath); - successCallback(entry); - }; - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(win, fail, "File", "getDirectory", [this.fullPath, path, options]); -}; - -/** - * Deletes a directory and all of it's contents - * - * @param {Function} successCallback is called with no parameters - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(successCallback, fail, "File", "removeRecursively", [this.fullPath]); -}; - -/** - * Creates or looks up a file - * - * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file - * @param {Flags} options to create or excluively create the file - * @param {Function} successCallback is called with the new entry - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var FileEntry = require('cordova/plugin/FileEntry'); - var entry = new FileEntry(result.name, result.fullPath); - successCallback(entry); - }; - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(win, fail, "File", "getFile", [this.fullPath, path, options]); -}; - -module.exports = DirectoryEntry; - -}); - -// file: lib/common/plugin/DirectoryReader.js -define("cordova/plugin/DirectoryReader", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * An interface that lists the files and directories in a directory. - */ -function DirectoryReader(path) { - this.path = path || null; -} - -/** - * Returns a list of entries from a directory. - * - * @param {Function} successCallback is called with a list of entries - * @param {Function} errorCallback is called with a FileError - */ -DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var retVal = []; - for (var i=0; i][;base64], - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsDataURL = function(file) { - this.fileName = ""; - if (typeof file.fullPath === "undefined") { - this.fileName = file; - } else { - this.fileName = file.fullPath; - } - - // Already loading something - if (this.readyState == FileReader.LOADING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // LOADING state - this.readyState = FileReader.LOADING; - - // If loadstart callback - if (typeof this.onloadstart === "function") { - this.onloadstart(new ProgressEvent("loadstart", {target:this})); - } - - var me = this; - - // Read file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileReader.DONE) { - return; - } - - // DONE state - me.readyState = FileReader.DONE; - - // Save result - me.result = r; - - // If onload callback - if (typeof me.onload === "function") { - me.onload(new ProgressEvent("load", {target:me})); - } - - // If onloadend callback - if (typeof me.onloadend === "function") { - me.onloadend(new ProgressEvent("loadend", {target:me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileReader.DONE) { - return; - } - - // DONE state - me.readyState = FileReader.DONE; - - me.result = null; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {target:me})); - } - - // If onloadend callback - if (typeof me.onloadend === "function") { - me.onloadend(new ProgressEvent("loadend", {target:me})); - } - }, "File", "readAsDataURL", [this.fileName]); -}; - -/** - * Read file and return data as a binary data. - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsBinaryString = function(file) { - // TODO - Can't return binary data to browser. - console.log('method "readAsBinaryString" is not supported at this time.'); -}; - -/** - * Read file and return data as a binary data. - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsArrayBuffer = function(file) { - // TODO - Can't return binary data to browser. - console.log('This method is not supported at this time.'); -}; - -module.exports = FileReader; - -}); - -// file: lib/common/plugin/FileSystem.js -define("cordova/plugin/FileSystem", function(require, exports, module) { -var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); - -/** - * An interface representing a file system - * - * @constructor - * {DOMString} name the unique name of the file system (readonly) - * {DirectoryEntry} root directory of the file system (readonly) - */ -var FileSystem = function(name, root) { - this.name = name || null; - if (root) { - this.root = new DirectoryEntry(root.name, root.fullPath); - } -}; - -module.exports = FileSystem; - -}); - -// file: lib/common/plugin/FileTransfer.js -define("cordova/plugin/FileTransfer", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * FileTransfer uploads a file to a remote server. - * @constructor - */ -var FileTransfer = function() {}; - -/** -* Given an absolute file path, uploads a file on the device to a remote server -* using a multipart HTTP request. -* @param filePath {String} Full path of the file on the device -* @param server {String} URL of the server to receive the file -* @param successCallback (Function} Callback to be invoked when upload has completed -* @param errorCallback {Function} Callback to be invoked upon error -* @param options {FileUploadOptions} Optional parameters such as file name and mimetype -* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false -*/ -FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) { - // check for options - var fileKey = null; - var fileName = null; - var mimeType = null; - var params = null; - var chunkedMode = true; - if (options) { - fileKey = options.fileKey; - fileName = options.fileName; - mimeType = options.mimeType; - if (options.chunkedMode !== null || typeof options.chunkedMode !== "undefined") { - chunkedMode = options.chunkedMode; - } - if (options.params) { - params = options.params; - } - else { - params = {}; - } - } - - exec(successCallback, errorCallback, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode]); -}; - -/** - * Downloads a file form a given URL and saves it to the specified directory. - * @param source {String} URL of the server to receive the file - * @param target {String} Full path of the file on the device - * @param successCallback (Function} Callback to be invoked when upload has completed - * @param errorCallback {Function} Callback to be invoked upon error - */ -FileTransfer.prototype.download = function(source, target, successCallback, errorCallback) { - var win = function(result) { - var entry = null; - if (result.isDirectory) { - entry = new DirectoryEntry(); - } - else if (result.isFile) { - entry = new FileEntry(); - } - entry.isDirectory = result.isDirectory; - entry.isFile = result.isFile; - entry.name = result.name; - entry.fullPath = result.fullPath; - successCallback(entry); - }; - exec(win, errorCallback, 'FileTransfer', 'download', [source, target]); -}; - -module.exports = FileTransfer; - -}); - -// file: lib/common/plugin/FileTransferError.js -define("cordova/plugin/FileTransferError", function(require, exports, module) { -/** - * FileTransferError - * @constructor - */ -var FileTransferError = function(code) { - this.code = code || null; -}; - -FileTransferError.FILE_NOT_FOUND_ERR = 1; -FileTransferError.INVALID_URL_ERR = 2; -FileTransferError.CONNECTION_ERR = 3; - -module.exports = FileTransferError; - -}); - -// file: lib/common/plugin/FileUploadOptions.js -define("cordova/plugin/FileUploadOptions", function(require, exports, module) { -/** - * Options to customize the HTTP request used to upload files. - * @constructor - * @param fileKey {String} Name of file request parameter. - * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. - * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. - * @param params {Object} Object with key: value params to send to the server. - */ -var FileUploadOptions = function(fileKey, fileName, mimeType, params) { - this.fileKey = fileKey || null; - this.fileName = fileName || null; - this.mimeType = mimeType || null; - this.params = params || null; -}; - -module.exports = FileUploadOptions; - -}); - -// file: lib/common/plugin/FileUploadResult.js -define("cordova/plugin/FileUploadResult", function(require, exports, module) { -/** - * FileUploadResult - * @constructor - */ -var FileUploadResult = function() { - this.bytesSent = 0; - this.responseCode = null; - this.response = null; -}; - -module.exports = FileUploadResult; - -}); - -// file: lib/common/plugin/FileWriter.js -define("cordova/plugin/FileWriter", function(require, exports, module) { -var exec = require('cordova/exec'), - FileError = require('cordova/plugin/FileError'); - ProgressEvent = require('cordova/plugin/ProgressEvent'); - -/** - * 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" - * - * @constructor - * @param file {File} File object containing file properties - * @param append if true write to the end of the file, otherwise overwrite the file - */ -var FileWriter = function(file) { - this.fileName = ""; - this.length = 0; - if (file) { - this.fileName = file.fullPath || file; - this.length = file.size || 0; - } - // default is to write at the beginning of the file - this.position = 0; - - 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() { - // check for invalid state - if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // set error - this.error = new FileError(FileError.ABORT_ERR); - - this.readyState = FileWriter.DONE; - - // If abort callback - if (typeof this.onabort === "function") { - this.onabort(new ProgressEvent("abort", {"target":this})); - } - - // If write end callback - if (typeof this.onwriteend === "function") { - this.onwriteend(new ProgressEvent("writeend", {"target":this})); - } -}; - -/** - * Writes data to the file - * - * @param text to be written - */ -FileWriter.prototype.write = function(text) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // WRITING state - this.readyState = FileWriter.WRITING; - - var me = this; - - // If onwritestart callback - if (typeof me.onwritestart === "function") { - me.onwritestart(new ProgressEvent("writestart", {"target":me})); - } - - // Write file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // position always increases by bytes written because file would be extended - me.position += r; - // The length of the file is now where we are done writing. - - me.length = me.position; - - // DONE state - me.readyState = FileWriter.DONE; - - // If onwrite callback - if (typeof me.onwrite === "function") { - me.onwrite(new ProgressEvent("write", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, "File", "write", [this.fileName, text, this.position]); -}; - -/** - * Moves the file pointer to the location specified. - * - * If the offset is a negative number the position of the file - * pointer is rewound. If the offset is greater than the file - * size the position is set to the end of the file. - * - * @param offset is the location to move the file pointer to. - */ -FileWriter.prototype.seek = function(offset) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - if (!offset) { - return; - } - - // See back from end of file. - if (offset < 0) { - this.position = Math.max(offset + this.length, 0); - } - // Offset is bigger then file size so set position - // to the end of the file. - else if (offset > this.length) { - this.position = this.length; - } - // Offset is between 0 and file size so set the position - // to start writing. - else { - this.position = offset; - } -}; - -/** - * Truncates the file to the size specified. - * - * @param size to chop the file at. - */ -FileWriter.prototype.truncate = function(size) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // WRITING state - this.readyState = FileWriter.WRITING; - - var me = this; - - // If onwritestart callback - if (typeof me.onwritestart === "function") { - me.onwritestart(new ProgressEvent("writestart", {"target":this})); - } - - // Write file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Update the length of the file - me.length = r; - me.position = Math.min(me.position, r); - - // If onwrite callback - if (typeof me.onwrite === "function") { - me.onwrite(new ProgressEvent("write", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, "File", "truncate", [this.fileName, size]); -}; - -module.exports = FileWriter; - -}); - -// file: lib/common/plugin/Flags.js -define("cordova/plugin/Flags", function(require, exports, module) { -/** - * Supplies arguments to methods that lookup or create files and directories. - * - * @param create - * {boolean} file or directory if it doesn't exist - * @param exclusive - * {boolean} used with create; if true the command will fail if - * target path exists - */ -function Flags(create, exclusive) { - this.create = create || false; - this.exclusive = exclusive || false; -} - -module.exports = Flags; - -}); - -// file: lib/common/plugin/LocalFileSystem.js -define("cordova/plugin/LocalFileSystem", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * Represents a local file system. - */ -var LocalFileSystem = function() { - -}; - -LocalFileSystem.TEMPORARY = 0; //temporary, with no guarantee of persistence -LocalFileSystem.PERSISTENT = 1; //persistent - -module.exports = LocalFileSystem; - -}); - -// file: lib/common/plugin/Media.js -define("cordova/plugin/Media", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'); - -var mediaObjects = {}; - -/** - * This class provides access to the device media, interfaces to both sound and video - * - * @constructor - * @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() - * @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 - */ -var Media = function(src, successCallback, errorCallback, statusCallback) { - - // 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; - } - - this.id = utils.createUUID(); - mediaObjects[this.id] = this; - this.src = src; - this.successCallback = successCallback; - this.errorCallback = errorCallback; - this.statusCallback = statusCallback; - this._duration = -1; - this._position = -1; - exec(null, this.errorCallback, "Media", "create", [this.id, this.src]); -}; - -// Media messages -Media.MEDIA_STATE = 1; -Media.MEDIA_DURATION = 2; -Media.MEDIA_POSITION = 3; -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"]; - -// "static" function to return existing objs. -Media.get = function(id) { - return mediaObjects[id]; -}; - -/** - * Start or resume playing audio file. - */ -Media.prototype.play = function(options) { - exec(null, null, "Media", "startPlayingAudio", [this.id, this.src, options]); -}; - -/** - * Stop playing audio file. - */ -Media.prototype.stop = function() { - var me = this; - exec(function() { - me._position = 0; - me.successCallback(); - }, this.errorCallback, "Media", "stopPlayingAudio", [this.id]); -}; - -/** - * Seek or jump to a new time in the track.. - */ -Media.prototype.seekTo = function(milliseconds) { - var me = this; - exec(function(p) { - me._position = p; - }, this.errorCallback, "Media", "seekToAudio", [this.id, milliseconds]); -}; - -/** - * Pause playing audio file. - */ -Media.prototype.pause = function() { - exec(null, this.errorCallback, "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. - */ -Media.prototype.getCurrentPosition = function(success, fail) { - var me = this; - exec(function(p) { - me._position = p; - success(p); - }, fail, "Media", "getCurrentPositionAudio", [this.id]); -}; - -/** - * Start recording audio file. - */ -Media.prototype.startRecord = function() { - exec(this.successCallback, this.errorCallback, "Media", "startRecordingAudio", [this.id, this.src]); -}; - -/** - * Stop recording audio file. - */ -Media.prototype.stopRecord = function() { - exec(this.successCallback, this.errorCallback, "Media", "stopRecordingAudio", [this.id]); -}; - -/** - * Release the resources. - */ -Media.prototype.release = function() { - exec(null, this.errorCallback, "Media", "release", [this.id]); -}; - -/** - * Adjust the volume. - */ -Media.prototype.setVolume = function(volume) { - exec(null, null, "Media", "setVolume", [this.id, volume]); -}; - -/** - * Audio has status update. - * PRIVATE - * - * @param id The media object id (string) - * @param status The status code (int) - * @param msg The status message (string) - */ -Media.onStatus = function(id, msg, value) { - var media = 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) { - // value should be a MediaError object when msg == MEDIA_ERROR - media.errorCallback(value); - } - } - else if (msg === Media.MEDIA_POSITION) { - media._position = value; - } -}; - -module.exports = Media; - -}); - -// file: lib/common/plugin/MediaError.js -define("cordova/plugin/MediaError", function(require, exports, module) { -/** - * This class contains information about any Media errors. - * @constructor - */ -var MediaError = function(code, msg) { - this.code = (code !== undefined ? code : null); - this.message = msg || ""; -}; - -MediaError.MEDIA_ERR_NONE_ACTIVE = 0; -MediaError.MEDIA_ERR_ABORTED = 1; -MediaError.MEDIA_ERR_NETWORK = 2; -MediaError.MEDIA_ERR_DECODE = 3; -MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; - -module.exports = MediaError; - -}); - -// file: lib/common/plugin/MediaFile.js -define("cordova/plugin/MediaFile", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - File = require('cordova/plugin/File'), - CaptureError = require('cordova/plugin/CaptureError'); -/** - * Represents a single file. - * - * name {DOMString} name of the file, without path information - * fullPath {DOMString} the full path of the file, including the name - * type {DOMString} mime type - * lastModifiedDate {Date} last modified date - * size {Number} size of the file in bytes - */ -var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ - MediaFile.__super__.constructor.apply(this, arguments); -}; - -utils.extend(MediaFile, File); - -/** - * Request capture format data for a specific file and type - * - * @param {Function} successCB - * @param {Function} errorCB - */ -MediaFile.prototype.getFormatData = function(successCallback, errorCallback) { - if (typeof this.fullPath === "undefined" || this.fullPath === null) { - errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); - } else { - exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); - } -}; - -/** - * Casts a PluginResult message property (array of objects) to an array of MediaFile objects - * (used in Objective-C and Android) - * - * @param {PluginResult} pluginResult - */ -MediaFile.cast = function(pluginResult) { - var mediaFiles = []; - var i; - for (i=0; i.dispatchEvent - // need to first figure out how to implement EventTarget - } - } - return event; - }; - try { - var ev = createEvent({type:"abort",target:document}); - return function ProgressEvent(type, data) { - data.type = type; - return createEvent(data); - }; - } catch(e){ - */ - return function ProgressEvent(type, dict) { - this.type = type; - this.bubbles = false; - this.cancelBubble = false; - this.cancelable = false; - this.lengthComputable = false; - this.loaded = dict && dict.loaded ? dict.loaded : 0; - this.total = dict && dict.total ? dict.total : 0; - this.target = dict && dict.target ? dict.target : null; - }; - //} -})(); - -module.exports = ProgressEvent; - -}); - -// file: lib/common/plugin/accelerometer.js -define("cordova/plugin/accelerometer", function(require, exports, module) { -/** - * This class provides access to device accelerometer data. - * @constructor - */ -var utils = require("cordova/utils"), - exec = require("cordova/exec"); - -// Local singleton variables. -var timers = {}; - -var accelerometer = { - /** - * Asynchronously aquires the current acceleration. - * - * @param {Function} successCallback The function to call when the acceleration data is available - * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) - * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) - */ - getCurrentAcceleration: function(successCallback, errorCallback, options) { - - // successCallback required - if (typeof successCallback !== "function") { - console.log("Accelerometer Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Accelerometer Error: errorCallback is not a function"); - return; - } - - // Get acceleration - exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); - }, - - /** - * Asynchronously aquires the acceleration repeatedly at a given interval. - * - * @param {Function} successCallback The function to call each time the acceleration data is available - * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) - * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) - * @return String The watch id that must be passed to #clearWatch to stop watching. - */ - watchAcceleration: function(successCallback, errorCallback, options) { - - // Default interval (10 sec) - var frequency = (options !== undefined && options.frequency !== undefined)? options.frequency : 10000; - - // successCallback required - if (typeof successCallback !== "function") { - console.log("Accelerometer Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Accelerometer Error: errorCallback is not a function"); - return; - } - - // Make sure accelerometer timeout > frequency + 10 sec - exec( - function(timeout) { - if (timeout < (frequency + 10000)) { - exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); - } - }, - function(e) { }, "Accelerometer", "getTimeout", []); - - // Start watch timer - var id = utils.createUUID(); - timers[id] = window.setInterval(function() { - 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. - */ - clearWatch: function(id) { - - // Stop javascript timer & remove from timer list - if (id && timers[id] !== undefined) { - window.clearInterval(timers[id]); - delete timers[id]; - } - } -}; - -module.exports = accelerometer; - -}); - -// file: lib/android/plugin/android/app.js -define("cordova/plugin/android/app", function(require, exports, module) { -var exec = require('cordova/exec'); - -module.exports = { - /** - * Clear the resource cache. - */ - clearCache:function() { - exec(null, null, "App", "clearCache", []); - }, - - /** - * Load the url into the webview or into new browser instance. - * - * @param url The URL to load - * @param props Properties that can be passed in to the activity: - * wait: int => wait msec before loading URL - * loadingDialog: "Title,Message" => display a native loading dialog - * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error - * clearHistory: boolean => clear webview history (default=false) - * openExternal: boolean => open in a new browser (default=false) - * - * Example: - * navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); - */ - loadUrl:function(url, props) { - exec(null, null, "App", "loadUrl", [url, props]); - }, - - /** - * Cancel loadUrl that is waiting to be loaded. - */ - cancelLoadUrl:function() { - exec(null, null, "App", "cancelLoadUrl", []); - }, - - /** - * Clear web history in this web view. - * Instead of BACK button loading the previous web page, it will exit the app. - */ - clearHistory:function() { - exec(null, null, "App", "clearHistory", []); - }, - - /** - * Go to previous page displayed. - * This is the same as pressing the backbutton on Android device. - */ - backHistory:function() { - exec(null, null, "App", "backHistory", []); - }, - - /** - * Override the default behavior of the Android back button. - * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. - * - * Note: The user should not have to call this method. Instead, when the user - * registers for the "backbutton" event, this is automatically done. - * - * @param override T=override, F=cancel override - */ - overrideBackbutton:function(override) { - exec(null, null, "App", "overrideBackbutton", [override]); - }, - - /** - * Exit and terminate the application. - */ - exitApp:function() { - return exec(null, null, "App", "exitApp", []); - } -}; - -}); - -// file: lib/android/plugin/android/callback.js -define("cordova/plugin/android/callback", function(require, exports, module) { -var port = null, - token = null, - cordova = require('cordova'), - polling = require('cordova/plugin/android/polling'), - callback = function() { - // Exit if shutting down app - if (cordova.shuttingDown) { - return; - } - - // If polling flag was changed, start using polling from now on - if (cordova.UsePolling) { - polling(); - return; - } - - var xmlhttp = new XMLHttpRequest(); - - // Callback function when XMLHttpRequest is ready - xmlhttp.onreadystatechange=function(){ - if(xmlhttp.readyState === 4){ - - // Exit if shutting down app - if (cordova.shuttingDown) { - return; - } - - // If callback has JavaScript statement to execute - if (xmlhttp.status === 200) { - - // Need to url decode the response - var msg = decodeURIComponent(xmlhttp.responseText); - setTimeout(function() { - try { - var t = eval(msg); - } - catch (e) { - // If we're getting an error here, seeing the message will help in debugging - console.log("JSCallback: Message from Server: " + msg); - console.log("JSCallback Error: "+e); - } - }, 1); - setTimeout(callback, 1); - } - - // If callback ping (used to keep XHR request from timing out) - else if (xmlhttp.status === 404) { - setTimeout(callback, 10); - } - - // If security error - else if (xmlhttp.status === 403) { - console.log("JSCallback Error: Invalid token. Stopping callbacks."); - } - - // If server is stopping - else if (xmlhttp.status === 503) { - console.log("JSCallback Server Closed: Stopping callbacks."); - } - - // If request wasn't GET - else if (xmlhttp.status === 400) { - console.log("JSCallback Error: Bad request. Stopping callbacks."); - } - - // If error, revert to polling - else { - console.log("JSCallback Error: Request failed."); - cordova.UsePolling = true; - polling(); - } - } - }; - - if (port === null) { - port = prompt("getPort", "gap_callbackServer:"); - } - if (token === null) { - token = prompt("getToken", "gap_callbackServer:"); - } - xmlhttp.open("GET", "http://127.0.0.1:"+port+"/"+token , true); - xmlhttp.send(); -}; - -module.exports = callback; - -}); - -// file: lib/android/plugin/android/device.js -define("cordova/plugin/android/device", function(require, exports, module) { -var channel = require('cordova/channel'), - utils = require('cordova/utils'), - exec = require('cordova/exec'); - -/** - * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the - * phone, etc. - * @constructor - */ -function Device() { - this.available = false; - this.platform = null; - this.version = null; - this.name = null; - this.uuid = null; - this.cordova = null; - - var me = this; - - channel.onCordovaReady.subscribeOnce(function() { - me.getInfo(function(info) { - me.available = true; - me.platform = info.platform; - me.version = info.version; - me.name = info.name; - me.uuid = info.uuid; - me.cordova = info.cordova; - channel.onCordovaInfoReady.fire(); - },function(e) { - me.available = false; - utils.alert("[ERROR] Error initializing Cordova: " + 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 - exec(successCallback, errorCallback, "Device", "getDeviceInfo", []); -}; - -/* - * DEPRECATED - * This is only for Android. - * - * You must explicitly override the back button. - */ -Device.prototype.overrideBackButton = function() { - console.log("Device.overrideBackButton() is deprecated. Use App.overrideBackbutton(true)."); - navigator.app.overrideBackbutton(true); -}; - -/* - * DEPRECATED - * This is only for Android. - * - * This resets the back button to the default behaviour - */ -Device.prototype.resetBackButton = function() { - console.log("Device.resetBackButton() is deprecated. Use App.overrideBackbutton(false)."); - navigator.app.overrideBackbutton(false); -}; - -/* - * DEPRECATED - * This is only for Android. - * - * This terminates the activity! - */ -Device.prototype.exitApp = function() { - console.log("Device.exitApp() is deprecated. Use App.exitApp()."); - navigator.app.exitApp(); -}; - -module.exports = new Device(); - -}); - -// file: lib/android/plugin/android/notification.js -define("cordova/plugin/android/notification", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * Provides Android enhanced notification API. - */ -module.exports = { - activityStart : function(title, message) { - // If title and message not specified then mimic Android behavior of - // using default strings. - if (typeof title === "undefined" && typeof message == "undefined") { - title = "Busy"; - message = 'Please wait...'; - } - - exec(null, null, 'Notification', 'activityStart', [ title, message ]); - }, - - /** - * Close an activity dialog - */ - activityStop : function() { - 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. - */ - progressStart : function(title, message) { - exec(null, null, 'Notification', 'progressStart', [ title, message ]); - }, - - /** - * Close the progress dialog. - */ - progressStop : function() { - exec(null, null, 'Notification', 'progressStop', []); - }, - - /** - * Set the progress dialog value. - * - * @param {Number} - * value 0-100 - */ - progressValue : function(value) { - exec(null, null, 'Notification', 'progressValue', [ value ]); - }, -}; -}); - -// file: lib/android/plugin/android/polling.js -define("cordova/plugin/android/polling", function(require, exports, module) { -var cordova = require('cordova'), - period = 50, - polling = function() { - // Exit if shutting down app - if (cordova.shuttingDown) { - return; - } - - // If polling flag was changed, stop using polling from now on and switch to XHR server / callback - if (!cordova.UsePolling) { - require('cordova/plugin/android/callback')(); - return; - } - - var msg = prompt("", "gap_poll:"); - if (msg) { - setTimeout(function() { - try { - var t = eval(""+msg); - } - catch (e) { - console.log("JSCallbackPolling: Message from Server: " + msg); - console.log("JSCallbackPolling Error: "+e); - } - }, 1); - setTimeout(polling, 1); - } - else { - setTimeout(polling, period); - } -}; - -module.exports = polling; - -}); - -// file: lib/android/plugin/android/storage.js -define("cordova/plugin/android/storage", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'); - channel = require('cordova/channel'); - -var queryQueue = {}; - -/** - * SQL result set object - * PRIVATE METHOD - * @constructor - */ -var 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]; -}; - -/** - * SQL result set that is returned to user. - * PRIVATE METHOD - * @constructor - */ -var DroidDB_Result = function() { - this.rows = new DroidDB_Rows(); -}; - -/** - * Callback from native code when query is complete. - * PRIVATE METHOD - * - * @param id Query id - */ -function completeQuery(id, data) { - var query = queryQueue[id]; - if (query) { - try { - delete 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 = data; - r.rows.length = data.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 - */ -function failQuery(reason, id) { - var query = queryQueue[id]; - if (query) { - try { - delete 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); - } - } -} - -/** - * SQL query object - * PRIVATE METHOD - * - * @constructor - * @param tx The transaction object that this query belongs to - */ -var DroidDB_Query = function(tx) { - - // Set the id of the query - this.id = utils.createUUID(); - - // Add this query to the queue - 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; - -}; - -/** - * Transaction object - * PRIVATE METHOD - * @constructor - */ -var DroidDB_Tx = function() { - - // Set the id of the transaction - this.id = utils.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; - var i; - for (i in this.queryList) { - if (this.queryList.hasOwnProperty(i)) { - 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); - } - } -}; - -/** - * 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); - queryQueue[query.id] = query; - - // Save callbacks - query.successCallback = successCallback; - query.errorCallback = errorCallback; - - // Call native code - exec(null, null, "Storage", "executeSql", [sql, params, query.id]); -}; - -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, errorCallback, successCallback) { - 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); - } - } - } -}; - -/** - * 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 - */ -var DroidDB_openDatabase = function(name, version, display_name, size) { - exec(null, null, "Storage", "openDatabase", [name, version, display_name, size]); - var db = new DatabaseShell(); - return db; -}; - -/** - * For browsers with no localStorage we emulate it with SQLite. Follows the w3c api. - * TODO: Do similar for sessionStorage. - * @constructor - */ -var CupcakeLocalStorage = function() { - channel.waitForInitialization("cupcakeStorage"); - - try { - - this.db = openDatabase('localStorage', '1.0', 'localStorage', 2621440); - var storage = {}; - this.length = 0; - function setLength (length) { - this.length = length; - localStorage.length = length; - } - this.db.transaction( - function (transaction) { - var i; - transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); - transaction.executeSql('SELECT * FROM storage', [], function(tx, result) { - for(var i = 0; i < result.rows.length; i++) { - storage[result.rows.item(i)['id']] = result.rows.item(i)['body']; - } - setLength(result.rows.length); - channel.initializationComplete("cupcakeStorage"); - }); - - }, - function (err) { - utils.alert(err.message); - } - ); - this.setItem = function(key, val) { - if (typeof(storage[key])=='undefined') { - this.length++; - } - storage[key] = val; - this.db.transaction( - function (transaction) { - transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); - transaction.executeSql('REPLACE INTO storage (id, body) values(?,?)', [key,val]); - } - ); - }; - this.getItem = function(key) { - return storage[key]; - }; - this.removeItem = function(key) { - delete storage[key]; - this.length--; - this.db.transaction( - function (transaction) { - transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); - transaction.executeSql('DELETE FROM storage where id=?', [key]); - } - ); - }; - this.clear = function() { - storage = {}; - this.length = 0; - this.db.transaction( - function (transaction) { - transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); - transaction.executeSql('DELETE FROM storage', []); - } - ); - }; - this.key = function(index) { - var i = 0; - for (var j in storage) { - if (i==index) { - return j; - } else { - i++; - } - } - return null; - }; - - } catch(e) { - utils.alert("Database error "+e+"."); - return; - } -}; - -module.exports = { - openDatabase:DroidDB_openDatabase, - CupcakeLocalStorage:CupcakeLocalStorage, - failQuery:failQuery, - completeQuery:completeQuery -}; - -}); - -// file: lib/common/plugin/battery.js -define("cordova/plugin/battery", function(require, exports, module) { -/** - * This class contains information about the current battery status. - * @constructor - */ -var cordova = require('cordova'), - exec = require('cordova/exec'); - -function handlers() { - return battery.channels.batterystatus.numHandlers + - battery.channels.batterylow.numHandlers + - battery.channels.batterycritical.numHandlers; -} - -var Battery = function() { - this._level = null; - this._isPlugged = null; - // Create new event handlers on the window (returns a channel instance) - var subscriptionEvents = { - onSubscribe:this.onSubscribe, - onUnsubscribe:this.onUnsubscribe - }; - this.channels = { - batterystatus:cordova.addWindowEventHandler("batterystatus", subscriptionEvents), - batterylow:cordova.addWindowEventHandler("batterylow", subscriptionEvents), - batterycritical:cordova.addWindowEventHandler("batterycritical", subscriptionEvents) - }; -}; -/** - * Event handlers for when callbacks get registered for the battery. - * Keep track of how many handlers we have so we can start and stop the native battery listener - * appropriately (and hopefully save on battery life!). - */ -Battery.prototype.onSubscribe = function() { - var me = battery; - // If we just registered the first handler, make sure native listener is started. - if (handlers() === 1) { - exec(me._status, me._error, "Battery", "start", []); - } -}; - -Battery.prototype.onUnsubscribe = function() { - var me = battery; - - // If we just unregistered the last handler, make sure native listener is stopped. - if (handlers() === 0) { - exec(null, null, "Battery", "stop", []); - } -}; - -/** - * Callback for battery status - * - * @param {Object} info keys: level, isPlugged - */ -Battery.prototype._status = function(info) { - if (info) { - var me = battery; - var level = info.level; - if (me._level !== level || me._isPlugged !== info.isPlugged) { - // Fire batterystatus event - cordova.fireWindowEvent("batterystatus", info); - - // Fire low battery event - if (level === 20 || level === 5) { - if (level === 20) { - cordova.fireWindowEvent("batterylow", info); - } - else { - cordova.fireWindowEvent("batterycritical", info); - } - } - } - me._level = level; - me._isPlugged = info.isPlugged; - } -}; - -/** - * Error callback for battery start - */ -Battery.prototype._error = function(e) { - console.log("Error initializing Battery: " + e); -}; - -var battery = new Battery(); - -module.exports = battery; - -}); - -// file: lib/common/plugin/capture.js -define("cordova/plugin/capture", function(require, exports, module) { -var exec = require('cordova/exec'), - MediaFile = require('cordova/plugin/MediaFile'); - -/** - * Launches a capture of different types. - * - * @param (DOMString} type - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureVideoOptions} options - */ -function _capture(type, successCallback, errorCallback, options) { - var win = function(pluginResult) { - var mediaFiles = []; - var i; - for (i = 0; i < pluginResult.length; i++) { - var mediaFile = new MediaFile(); - mediaFile.name = pluginResult[i].name; - mediaFile.fullPath = pluginResult[i].fullPath; - mediaFile.type = pluginResult[i].type; - mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate; - mediaFile.size = pluginResult[i].size; - mediaFiles.push(mediaFile); - } - successCallback(mediaFiles); - }; - exec(win, errorCallback, "Capture", type, [options]); -} -/** - * The Capture interface exposes an interface to the camera and microphone of the hosting device. - */ -function Capture() { - this.supportedAudioModes = []; - this.supportedImageModes = []; - this.supportedVideoModes = []; -} - -/** - * Launch audio recorder application for recording audio clip(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureAudioOptions} options - */ -Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ - _capture("captureAudio", successCallback, errorCallback, options); -}; - -/** - * Launch camera application for taking image(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureImageOptions} options - */ -Capture.prototype.captureImage = function(successCallback, errorCallback, options){ - _capture("captureImage", successCallback, errorCallback, options); -}; - -/** - * Launch device camera application for recording video(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureVideoOptions} options - */ -Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ - _capture("captureVideo", successCallback, errorCallback, options); -}; - - -module.exports = new Capture(); - -}); - -// file: lib/common/plugin/compass.js -define("cordova/plugin/compass", function(require, exports, module) { -var exec = require('cordova/exec'), - utils = require('cordova/utils'), - CompassHeading = require('cordova/plugin/CompassHeading'), - CompassError = require('cordova/plugin/CompassError'), - timers = {}, - compass = { - /** - * Asynchronously acquires 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. - * @param {CompassOptions} options The options for getting the heading data (not used). - */ - 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; - } - - var win = function(result) { - var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); - successCallback(ch); - }; - var fail = function(code) { - var ce = new CompassError(code); - errorCallback(ce); - } - - // Get heading - exec(win, fail, "Compass", "getHeading", [options]); - }, - - /** - * Asynchronously acquires 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. - * @param {HeadingOptions} options The options for getting the heading data - * such as timeout and the frequency of the watch. For iOS, filter parameter - * specifies to watch via a distance filter rather than time. - */ - watchHeading:function(successCallback, errorCallback, options) { - // Default interval (100 msec) - var frequency = (options !== undefined && options.frequency !== undefined) ? options.frequency : 100; - var filter = (options !== undefined && options.filter !== undefined) ? options.filter : 0; - - // 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; - } - - var id = utils.createUUID(); - if (filter > 0) { - // is an iOS request for watch by filter, no timer needed - timers[id] = "iOS"; - compass.getCurrentHeading(successCallback, errorCallback, options); - } else { - // Start watch timer to get headings - timers[id] = window.setInterval(function() { - compass.getCurrentHeading(successCallback, errorCallback); - }, frequency); - } - - return id; - }, - - /** - * Clears the specified heading watch. - * @param {String} watchId The ID of the watch returned from #watchHeading. - */ - clearWatch:function(id) { - // Stop javascript timer & remove from timer list - if (id && timers[id]) { - if (timers[id] != "iOS") { - clearInterval(timers[id]); - } else { - // is iOS watch by filter so call into device to stop - exec(null, null, "Compass", "stopHeading", []); - } - delete timers[id]; - } - } - }; - -module.exports = compass; - -}); - -// file: lib/common/plugin/contacts.js -define("cordova/plugin/contacts", function(require, exports, module) { -var exec = require('cordova/exec'), - ContactError = require('cordova/plugin/ContactError'), - Contact = require('cordova/plugin/Contact'); - -/** -* Represents a group of Contacts. -* @constructor -*/ -var contacts = { - /** - * 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 - */ - find:function(fields, successCB, errorCB, options) { - if (!successCB) { - throw new TypeError("You must specify a success callback for the find command."); - } - if (!fields || (fields instanceof Array && fields.length === 0)) { - if (typeof errorCB === "function") { - errorCB(new ContactError(ContactError.INVALID_ARGUMENT_ERROR)); - } - } else { - var win = function(result) { - var cs = []; - for (var i = 0, l = result.length; i < l; i++) { - cs.push(contacts.create(result[i])); - } - successCB(cs); - }; - exec(win, 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 - */ - create:function(properties) { - var i; - var contact = new Contact(); - for (i in properties) { - if (typeof contact[i] !== 'undefined' && properties.hasOwnProperty(i)) { - contact[i] = properties[i]; - } - } - return contact; - } -}; - -module.exports = contacts; - -}); - -// file: lib/common/plugin/geolocation.js -define("cordova/plugin/geolocation", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - PositionError = require('cordova/plugin/PositionError'), - Position = require('cordova/plugin/Position'); - -var timers = {}; // list of timers in use - -// Returns default params, overrides if provided with values -function parseParameters(options) { - var opt = { - maximumAge: 10000, - enableHighAccuracy: false, - timeout: 10000 - }; - - if (options) { - if (options.maximumAge !== undefined) { - opt.maximumAge = options.maximumAge; - } - if (options.enableHighAccuracy !== undefined) { - opt.enableHighAccuracy = options.enableHighAccuracy; - } - if (options.timeout !== undefined) { - opt.timeout = options.timeout; - } - } - - return opt; -} - -var geolocation = { - /** - * 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) - */ - getCurrentPosition:function(successCallback, errorCallback, options) { - options = parseParameters(options); - - var win = function(p) { - successCallback(new Position( - { - latitude:p.latitude, - longitude:p.longitude, - altitude:p.altitude, - accuracy:p.accuracy, - heading:p.heading, - velocity:p.velocity, - altitudeAccuracy:p.altitudeAccuracy - }, - p.timestamp || new Date() - )); - }; - var fail = function(e) { - errorCallback(new PositionError(e.code, e.message)); - }; - - exec(win, fail, "Geolocation", "getLocation", [options.enableHighAccuracy, options.timeout, options.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. - */ - watchPosition:function(successCallback, errorCallback, options) { - options = parseParameters(options); - - var id = utils.createUUID(); - timers[id] = window.setInterval(function() { - geolocation.getCurrentPosition(successCallback, errorCallback, options); - }, options.timeout); - - return id; - }, - /** - * Clears the specified heading watch. - * - * @param {String} id The ID of the watch returned from #watchPosition - */ - clearWatch:function(id) { - if (id && timers[id] !== undefined) { - window.clearInterval(timers[id]); - delete timers[id]; - } - } -}; - -module.exports = geolocation; - -}); - -// file: lib/common/plugin/network.js -define("cordova/plugin/network", function(require, exports, module) { -var exec = require('cordova/exec'), - cordova = require('cordova'), - channel = require('cordova/channel'); - -var NetworkConnection = function () { - this.type = null; - this._firstRun = true; - this._timer = null; - this.timeout = 500; - - var me = this; - - channel.onCordovaReady.subscribeOnce(function() { - me.getInfo(function (info) { - me.type = info; - if (info === "none") { - // set a timer if still offline at the end of timer send the offline event - me._timer = setTimeout(function(){ - cordova.fireDocumentEvent("offline"); - me._timer = null; - }, me.timeout); - } else { - // If there is a current offline event pending clear it - if (me._timer !== null) { - clearTimeout(me._timer); - me._timer = null; - } - cordova.fireDocumentEvent("online"); - } - - // should only fire this once - if (me._firstRun) { - me._firstRun = false; - channel.onCordovaConnectionReady.fire(); - } - }, - function (e) { - // If we can't get the network info we should still tell Cordova - // to fire the deviceready event. - if (me._firstRun) { - me._firstRun = false; - channel.onCordovaConnectionReady.fire(); - } - console.log("Error initializing Network Connection: " + e); - }); - }); -}; - -/** - * Get connection info - * - * @param {Function} successCallback The function to call when the Connection data is available - * @param {Function} errorCallback The function to call when there is an error getting the Connection data. (OPTIONAL) - */ -NetworkConnection.prototype.getInfo = function (successCallback, errorCallback) { - // Get info - exec(successCallback, errorCallback, "NetworkStatus", "getConnectionInfo", []); -}; - -module.exports = new NetworkConnection(); - -}); - -// file: lib/common/plugin/notification.js -define("cordova/plugin/notification", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * Provides access to notifications on the device. - */ - -module.exports = { - - /** - * 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) - */ - alert: function(message, completeCallback, title, buttonLabel) { - var _title = (title || "Alert"); - var _buttonLabel = (buttonLabel || "OK"); - 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') - */ - confirm: function(message, resultCallback, title, buttonLabels) { - var _title = (title || "Confirm"); - var _buttonLabels = (buttonLabels || "OK,Cancel"); - exec(resultCallback, null, "Notification", "confirm", [message, _title, _buttonLabels]); - }, - - /** - * Causes the device to vibrate. - * - * @param {Integer} mills The number of milliseconds to vibrate for. - */ - vibrate: function(mills) { - 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. - */ - beep: function(count) { - exec(null, null, "Notification", "beep", [count]); - } -}; - -}); - -// file: lib/common/plugin/requestFileSystem.js -define("cordova/plugin/requestFileSystem", function(require, exports, module) { -var FileError = require('cordova/plugin/FileError'), - FileSystem = require('cordova/plugin/FileSystem'), - exec = require('cordova/exec'); - -/** - * Request a file system in which to store application data. - * @param type local file system type - * @param size indicates how much storage space, in bytes, the application expects to need - * @param successCallback invoked with a FileSystem object - * @param errorCallback invoked if error occurs retrieving file system - */ -var requestFileSystem = function(type, size, successCallback, errorCallback) { - var fail = function(code) { - if (typeof errorCallback === 'function') { - errorCallback(new FileError(code)); - } - }; - - if (type < 0 || type > 3) { - fail(FileError.SYNTAX_ERR); - } else { - // if successful, return a FileSystem object - var success = function(file_system) { - if (file_system) { - if (typeof successCallback === 'function') { - // grab the name and root from the file system object - var result = new FileSystem(file_system.name, file_system.root); - successCallback(result); - } - } - else { - // no FileSystem object returned - fail(FileError.NOT_FOUND_ERR); - } - }; - exec(success, fail, "File", "requestFileSystem", [type, size]); - } -}; - -module.exports = requestFileSystem; - -}); - -// file: lib/common/plugin/resolveLocalFileSystemURI.js -define("cordova/plugin/resolveLocalFileSystemURI", function(require, exports, module) { -var DirectoryEntry = require('cordova/plugin/DirectoryEntry'), - FileEntry = require('cordova/plugin/FileEntry'), - exec = require('cordova/exec'); - -/** - * Look up file system Entry referred to by local URI. - * @param {DOMString} uri URI referring to a local file or directory - * @param successCallback invoked with Entry object corresponding to URI - * @param errorCallback invoked if error occurs retrieving file system entry - */ -module.exports = function(uri, successCallback, errorCallback) { - // error callback - var fail = function(error) { - if (typeof errorCallback === 'function') { - errorCallback(new FileError(error)); - } - }; - // if successful, return either a file or directory entry - var success = function(entry) { - var result; - - if (entry) { - if (typeof successCallback === 'function') { - // create appropriate Entry object - result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath); - try { - successCallback(result); - } - catch (e) { - console.log('Error invoking callback: ' + e); - } - } - } - else { - // no Entry object returned - fail(FileError.NOT_FOUND_ERR); - } - }; - - exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]); -}; - -}); - -// file: lib/common/utils.js -define("cordova/utils", function(require, exports, module) { -function UUIDcreatePart(length) { - var uuidpart = ""; - for (var i=0; i + + + + + + Cordova Tests + + + + + +

Full Screen Test

+
+

Platform:  , Version:  

+

UUID:  , Name:  

+

Width:  , Height:   + , Color Depth:

+
+
+ The app should take over the entire screen.
+ The top Android status bar should not be shown. +
+ + diff --git a/test/assets/www/index.html b/test/assets/www/index.html index 0fd4eafa..563cbf8c 100755 --- a/test/assets/www/index.html +++ b/test/assets/www/index.html @@ -1,20 +1,20 @@ @@ -49,7 +49,10 @@ $ + + + diff --git a/test/proguard.cfg b/test/proguard.cfg new file mode 100644 index 00000000..b1cdf17b --- /dev/null +++ b/test/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/test/project.properties b/test/project.properties old mode 100755 new mode 100644 index 4bcb2f55..9aa0dfa8 --- a/test/project.properties +++ b/test/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=Google Inc.:Google APIs:14 +target=Google Inc.:Google APIs:15 diff --git a/test/res/layout/main.xml b/test/res/layout/main.xml old mode 100755 new mode 100644 index 721cf4ec..45e2d063 --- a/test/res/layout/main.xml +++ b/test/res/layout/main.xml @@ -18,14 +18,14 @@ under the License. --> - - + android:orientation="vertical" > + + + + diff --git a/test/res/values/strings.xml b/test/res/values/strings.xml old mode 100755 new mode 100644 index 010e98e0..9e13e360 --- a/test/res/values/strings.xml +++ b/test/res/values/strings.xml @@ -1,4 +1,4 @@ - CordovaTests + CordovaTests diff --git a/test/res/xml/cordova.xml b/test/res/xml/cordova.xml index 8f347e7d..4aebda4e 100755 --- a/test/res/xml/cordova.xml +++ b/test/res/xml/cordova.xml @@ -18,19 +18,19 @@ under the License. --> - + - - - + + + - + diff --git a/test/res/xml/plugins.xml b/test/res/xml/plugins.xml old mode 100755 new mode 100644 diff --git a/test/src/org/apache/cordova/test/ActivityPlugin.java b/test/src/org/apache/cordova/test/ActivityPlugin.java index 9c260476..553ef2ae 100755 --- a/test/src/org/apache/cordova/test/ActivityPlugin.java +++ b/test/src/org/apache/cordova/test/ActivityPlugin.java @@ -69,9 +69,9 @@ public class ActivityPlugin extends Plugin { public void startActivity(String className) { try { - Intent intent = new Intent().setClass(this.ctx.getContext(), Class.forName(className)); + Intent intent = new Intent().setClass(this.ctx.getActivity(), Class.forName(className)); LOG.d(TAG, "Starting activity %s", className); - this.ctx.startActivity(intent); + this.ctx.getActivity().startActivity(intent); } catch (ClassNotFoundException e) { e.printStackTrace(); LOG.e(TAG, "Error starting activity %s", className); diff --git a/framework/src/org/apache/cordova/PreferenceNode.java b/test/src/org/apache/cordova/test/CordovaActivity.java similarity index 65% rename from framework/src/org/apache/cordova/PreferenceNode.java rename to test/src/org/apache/cordova/test/CordovaActivity.java index d577df1c..f3a7839c 100644 --- a/framework/src/org/apache/cordova/PreferenceNode.java +++ b/test/src/org/apache/cordova/test/CordovaActivity.java @@ -16,19 +16,18 @@ specific language governing permissions and limitations under the License. */ -package org.apache.cordova; +package org.apache.cordova.test; -// represents the element from the W3C config.xml spec -// see http://www.w3.org/TR/widgets/#the-preference-element-and-its-attributes -public class PreferenceNode { - public String name; - public String value; - public boolean readonly; +import org.apache.cordova.DroidGap; - // constructor - public PreferenceNode(String name, String value, boolean readonly) { - this.name = name; - this.value = value; - this.readonly = readonly; +import android.app.Activity; +import android.os.Bundle; + +public class CordovaActivity extends DroidGap { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + super.loadUrl("file:///android_asset/www/index.html"); } } diff --git a/test/src/org/apache/cordova/test/CordovaActivityTest.java b/test/src/org/apache/cordova/test/CordovaActivityTest.java new file mode 100644 index 00000000..b53f000a --- /dev/null +++ b/test/src/org/apache/cordova/test/CordovaActivityTest.java @@ -0,0 +1,68 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import com.phonegap.api.PluginManager; + +import android.test.ActivityInstrumentationTestCase2; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class CordovaActivityTest extends ActivityInstrumentationTestCase2 { + + private CordovaActivity testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private CordovaWebView testView; + + @SuppressWarnings("deprecation") + public CordovaActivityTest() + { + super("org.apache.cordova.test",CordovaActivity.class); + } + + protected void setUp() throws Exception { + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = (CordovaWebView) innerContainer.getChildAt(0); + + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + + public void testForCordovaView() { + String className = testView.getClass().getSimpleName(); + assertTrue(className.equals("CordovaWebView")); + } + + public void testForLinearLayout() { + String className = innerContainer.getClass().getSimpleName(); + assertTrue(className.equals("LinearLayoutSoftKeyboardDetect")); + } + + +} diff --git a/test/src/org/apache/cordova/test/CordovaTest.java b/test/src/org/apache/cordova/test/CordovaTest.java new file mode 100644 index 00000000..b7edd099 --- /dev/null +++ b/test/src/org/apache/cordova/test/CordovaTest.java @@ -0,0 +1,109 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import com.phonegap.api.PluginManager; + +import android.test.ActivityInstrumentationTestCase2; +import android.view.View; + +public class CordovaTest extends + ActivityInstrumentationTestCase2 { + + private static final long TIMEOUT = 1000; + private CordovaWebViewTestActivity testActivity; + private View testView; + private String rString; + + public CordovaTest() { + super("com.phonegap.test.activities", CordovaWebViewTestActivity.class); + } + + protected void setUp() throws Exception { + super.setUp(); + testActivity = this.getActivity(); + testView = testActivity.findViewById(R.id.phoneGapView); + } + + public void testPreconditions() { + assertNotNull(testView); + } + + public void testForCordovaView() { + String className = testView.getClass().getSimpleName(); + assertTrue(className.equals("CordovaWebView")); + } +/* + public void testForPluginManager() { + CordovaWebView v = (CordovaWebView) testView; + PluginManager p = v.getPluginManager(); + assertNotNull(p); + String className = p.getClass().getSimpleName(); + assertTrue(className.equals("PluginManager")); + } + + public void testBackButton() { + CordovaWebView v = (CordovaWebView) testView; + assertFalse(v.checkBackKey()); + } + + public void testLoadUrl() { + CordovaWebView v = (CordovaWebView) testView; + v.loadUrlIntoView("file:///android_asset/www/index.html"); + sleep(); + String url = v.getUrl(); + boolean result = url.equals("file:///android_asset/www/index.html"); + assertTrue(result); + int visible = v.getVisibility(); + assertTrue(visible == View.VISIBLE); + } + + public void testBackHistoryFalse() { + CordovaWebView v = (CordovaWebView) testView; + // Move back in the history + boolean test = v.backHistory(); + assertFalse(test); + } + + // Make sure that we can go back + public void testBackHistoryTrue() { + this.testLoadUrl(); + CordovaWebView v = (CordovaWebView) testView; + v.loadUrlIntoView("file:///android_asset/www/compass/index.html"); + sleep(); + String url = v.getUrl(); + assertTrue(url.equals("file:///android_asset/www/compass/index.html")); + // Move back in the history + boolean test = v.backHistory(); + assertTrue(test); + sleep(); + url = v.getUrl(); + assertTrue(url.equals("file:///android_asset/www/index.html")); + } + */ + + private void sleep() { + try { + Thread.sleep(TIMEOUT); + } catch (InterruptedException e) { + fail("Unexpected Timeout"); + } + } +} diff --git a/test/src/org/apache/cordova/test/CordovaWebViewTestActivity.java b/test/src/org/apache/cordova/test/CordovaWebViewTestActivity.java new file mode 100644 index 00000000..6c581067 --- /dev/null +++ b/test/src/org/apache/cordova/test/CordovaWebViewTestActivity.java @@ -0,0 +1,96 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.api.CordovaInterface; +import org.apache.cordova.api.IPlugin; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +public class CordovaWebViewTestActivity extends Activity implements CordovaInterface { + + CordovaWebView phoneGap; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.main); + + phoneGap = (CordovaWebView) findViewById(R.id.phoneGapView); + + phoneGap.loadUrl("file:///android_asset/www/index.html"); + + } + + public void onDestroy() + { + super.onDestroy(); + if (phoneGap.pluginManager != null) { + phoneGap.pluginManager.onDestroy(); + } + } + + @Override + public void startActivityForResult(IPlugin command, Intent intent, int requestCode) { + // TODO Auto-generated method stub + + } + + @Override + public void setActivityResultCallback(IPlugin plugin) { + // TODO Auto-generated method stub + + } + + @Override + public void bindBackButton(boolean override) { + // TODO Auto-generated method stub + + } + + @Override + public boolean isBackButtonBound() { + // TODO Auto-generated method stub + return false; + } + + @Override + public Activity getActivity() { + // TODO Auto-generated method stub + return this; + } + + @Override + public void cancelLoadUrl() { + // TODO Auto-generated method stub + + } + + @Override + public Object onMessage(String id, Object data) { + // TODO Auto-generated method stub + return null; + } +} \ No newline at end of file diff --git a/test/src/org/apache/cordova/test/ErrorUrlTest.java b/test/src/org/apache/cordova/test/ErrorUrlTest.java new file mode 100644 index 00000000..59b1742e --- /dev/null +++ b/test/src/org/apache/cordova/test/ErrorUrlTest.java @@ -0,0 +1,54 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; + +import android.test.ActivityInstrumentationTestCase2; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class ErrorUrlTest extends ActivityInstrumentationTestCase2 { + + private int TIMEOUT = 1000; + errorurl testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private CordovaWebView testView; + + public ErrorUrlTest() { + super("org.apache.cordova.test",errorurl.class); + } + + + protected void setUp() throws Exception { + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = (CordovaWebView) innerContainer.getChildAt(0); + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + public void testUrl() + { + sleep(); + String good_url = "file:///android_asset/www/htmlnotfound/error.html"; + String url = testView.getUrl(); + assertNotNull(url); + assertTrue(url.equals(good_url)); + } + + + private void sleep() { + try { + Thread.sleep(TIMEOUT); + } catch (InterruptedException e) { + fail("Unexpected Timeout"); + } + } + + +} diff --git a/test/src/org/apache/cordova/test/GapClientTest.java b/test/src/org/apache/cordova/test/GapClientTest.java new file mode 100644 index 00000000..bfee8638 --- /dev/null +++ b/test/src/org/apache/cordova/test/GapClientTest.java @@ -0,0 +1,67 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.CordovaChromeClient; +import org.apache.cordova.api.PluginManager; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.test.ActivityInstrumentationTestCase2; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class GapClientTest extends ActivityInstrumentationTestCase2 { + + private CordovaWebViewTestActivity testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private View testView; + private String rString; + private CordovaChromeClient appCode; + + public GapClientTest() { + super("com.phonegap.test.activities",CordovaWebViewTestActivity.class); + } + + protected void setUp() throws Exception{ + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = innerContainer.getChildAt(0); + + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + public void testForCordovaView() { + String className = testView.getClass().getSimpleName(); + assertTrue(className.equals("CordovaWebView")); + } + + +} diff --git a/test/src/org/apache/cordova/test/HtmlNotFoundTest.java b/test/src/org/apache/cordova/test/HtmlNotFoundTest.java new file mode 100644 index 00000000..89eb195e --- /dev/null +++ b/test/src/org/apache/cordova/test/HtmlNotFoundTest.java @@ -0,0 +1,52 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; + +import android.test.ActivityInstrumentationTestCase2; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class HtmlNotFoundTest extends ActivityInstrumentationTestCase2 { + + private int TIMEOUT = 1000; + private htmlnotfound testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private CordovaWebView testView; + + public HtmlNotFoundTest() { + super("org.apache.cordova.test",htmlnotfound.class); + } + + + protected void setUp() throws Exception { + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = (CordovaWebView) innerContainer.getChildAt(0); + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + public void testUrl() + { + sleep(); + String good_url = "file:///android_asset/www/htmlnotfound/error.html"; + String url = testView.getUrl(); + assertNotNull(url); + assertFalse(url.equals(good_url)); + } + + private void sleep() { + try { + Thread.sleep(TIMEOUT); + } catch (InterruptedException e) { + fail("Unexpected Timeout"); + } + } + +} diff --git a/test/src/org/apache/cordova/test/IFrameTest.java b/test/src/org/apache/cordova/test/IFrameTest.java new file mode 100644 index 00000000..07fba57a --- /dev/null +++ b/test/src/org/apache/cordova/test/IFrameTest.java @@ -0,0 +1,11 @@ +package org.apache.cordova.test; + +import android.test.ActivityInstrumentationTestCase2; + +public class IFrameTest extends ActivityInstrumentationTestCase2