diff --git a/framework/res/xml/config.xml b/framework/res/xml/config.xml index 935eda4f..6eea8d46 100644 --- a/framework/res/xml/config.xml +++ b/framework/res/xml/config.xml @@ -40,64 +40,9 @@ --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java deleted file mode 100755 index 934a8a8d..00000000 --- a/framework/src/org/apache/cordova/AccelListener.java +++ /dev/null @@ -1,285 +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.List; - -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaInterface; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; - -import android.os.Handler; -import android.os.Looper; - -/** - * This class listens to the accelerometer sensor and stores the latest - * acceleration values x,y,z. - */ -public class AccelListener extends CordovaPlugin implements SensorEventListener { - - public static int STOPPED = 0; - 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 int accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE; - - private SensorManager sensorManager; // Sensor manager - private Sensor mSensor; // Acceleration sensor returned by sensor manager - - private CallbackContext callbackContext; // Keeps track of the JS callback context. - - /** - * Create an accelerometer listener. - */ - public AccelListener() { - this.x = 0; - this.y = 0; - this.z = 0; - this.timestamp = 0; - this.setStatus(AccelListener.STOPPED); - } - - /** - * Sets the context of the Command. This can then be used to do things like - * get file paths associated with the Activity. - * - * @param cordova The context of the main Activity. - * @param webView The associated CordovaWebView. - */ - @Override - public void initialize(CordovaInterface cordova, CordovaWebView webView) { - super.initialize(cordova, webView); - this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE); - } - - /** - * Executes the request. - * - * @param action The action to execute. - * @param args The exec() arguments. - * @param callbackId The callback id used when calling back into JavaScript. - * @return Whether the action was valid. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) { - if (action.equals("start")) { - this.callbackContext = callbackContext; - if (this.status != AccelListener.RUNNING) { - // If not running, then this is an async call, so don't worry about waiting - // We drop the callback onto our stack, call start, and let start and the sensor callback fire off the callback down the road - this.start(); - } - } - else if (action.equals("stop")) { - if (this.status == AccelListener.RUNNING) { - this.stop(); - } - } else { - // Unsupported action - return false; - } - - PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT, ""); - result.setKeepCallback(true); - callbackContext.sendPluginResult(result); - return true; - } - - /** - * Called by AccelBroker when listener is to be shut down. - * Stop listener. - */ - public void onDestroy() { - this.stop(); - } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - // - /** - * Start listening for acceleration sensor. - * - * @return status of listener - */ - private int start() { - // If already starting or running, then just return - if ((this.status == AccelListener.RUNNING) || (this.status == AccelListener.STARTING)) { - return this.status; - } - - this.setStatus(AccelListener.STARTING); - - // Get accelerometer from sensor manager - List list = this.sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); - - // 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); - } 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; - } - - // Set a timeout callback on the main thread. - Handler handler = new Handler(Looper.getMainLooper()); - handler.postDelayed(new Runnable() { - public void run() { - AccelListener.this.timeout(); - } - }, 2000); - - return this.status; - } - - /** - * Stop listening to acceleration sensor. - */ - private void stop() { - if (this.status != AccelListener.STOPPED) { - this.sensorManager.unregisterListener(this); - } - this.setStatus(AccelListener.STOPPED); - this.accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE; - } - - /** - * Returns an error if the sensor hasn't started. - * - * Called two seconds after starting the listener. - */ - private void timeout() { - if (this.status == AccelListener.STARTING) { - this.setStatus(AccelListener.ERROR_FAILED_TO_START); - this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started."); - } - } - - /** - * Called when the accuracy of the sensor has changed. - * - * @param sensor - * @param accuracy - */ - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // Only look at accelerometer events - if (sensor.getType() != Sensor.TYPE_ACCELEROMETER) { - return; - } - - // If not running, then just return - if (this.status == AccelListener.STOPPED) { - return; - } - this.accuracy = accuracy; - } - - /** - * Sensor listener event. - * - * @param SensorEvent event - */ - public void onSensorChanged(SensorEvent event) { - // Only look at accelerometer events - if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { - return; - } - - // If not running, then just return - if (this.status == AccelListener.STOPPED) { - return; - } - this.setStatus(AccelListener.RUNNING); - - if (this.accuracy >= SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM) { - - // Save time that event was received - this.timestamp = System.currentTimeMillis(); - this.x = event.values[0]; - this.y = event.values[1]; - this.z = event.values[2]; - - this.win(); - } - } - - /** - * Called when the view navigates. - */ - @Override - public void onReset() { - if (this.status == AccelListener.RUNNING) { - this.stop(); - } - } - - // Sends an error back to JS - private void fail(int code, String message) { - // Error object - JSONObject errorObj = new JSONObject(); - try { - errorObj.put("code", code); - errorObj.put("message", message); - } catch (JSONException e) { - e.printStackTrace(); - } - PluginResult err = new PluginResult(PluginResult.Status.ERROR, errorObj); - err.setKeepCallback(true); - callbackContext.sendPluginResult(err); - } - - private void win() { - // Success return object - PluginResult result = new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON()); - result.setKeepCallback(true); - callbackContext.sendPluginResult(result); - } - - private void setStatus(int status) { - this.status = status; - } - private JSONObject getAccelerationJSON() { - JSONObject r = new JSONObject(); - try { - r.put("x", this.x); - r.put("y", this.y); - r.put("z", this.z); - r.put("timestamp", this.timestamp); - } catch (JSONException e) { - e.printStackTrace(); - } - return r; - } -} diff --git a/framework/src/org/apache/cordova/App.java b/framework/src/org/apache/cordova/App.java deleted file mode 100755 index afdbf3f2..00000000 --- a/framework/src/org/apache/cordova/App.java +++ /dev/null @@ -1,221 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.LOG; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; - -/** - * This class exposes methods in DroidGap that can be called from JavaScript. - */ -public class App extends CordovaPlugin { - - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackContext The callback context from which we were invoked. - * @return A PluginResult object with a status and message. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - PluginResult.Status status = PluginResult.Status.OK; - String result = ""; - - try { - if (action.equals("clearCache")) { - this.clearCache(); - } - else if (action.equals("show")) { - // This gets called from JavaScript onCordovaReady to show the webview. - // I recommend we change the name of the Message as spinner/stop is not - // indicative of what this actually does (shows the webview). - cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - webView.postMessage("spinner", "stop"); - } - }); - } - else if (action.equals("loadUrl")) { - this.loadUrl(args.getString(0), args.optJSONObject(1)); - } - else if (action.equals("cancelLoadUrl")) { - //this.cancelLoadUrl(); - } - else if (action.equals("clearHistory")) { - this.clearHistory(); - } - 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)); - } - else if (action.equals("exitApp")) { - this.exitApp(); - } - callbackContext.sendPluginResult(new PluginResult(status, result)); - return true; - } catch (JSONException e) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); - return false; - } - } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - /** - * Clear the resource cache. - */ - public void clearCache() { - this.webView.clearCache(true); - } - - /** - * Load the url into the webview. - * - * @param url - * @param props Properties that can be passed in to the DroidGap activity (i.e. loadingDialog, wait, ...) - * @throws JSONException - */ - public void loadUrl(String url, JSONObject props) throws JSONException { - LOG.d("App", "App.loadUrl("+url+","+props+")"); - int wait = 0; - boolean openExternal = false; - boolean clearHistory = false; - - // If there are properties, then set them on the Activity - HashMap params = new HashMap(); - if (props != null) { - JSONArray keys = props.names(); - for (int i = 0; i < keys.length(); i++) { - String key = keys.getString(i); - if (key.equals("wait")) { - wait = props.getInt(key); - } - else if (key.equalsIgnoreCase("openexternal")) { - openExternal = props.getBoolean(key); - } - else if (key.equalsIgnoreCase("clearhistory")) { - clearHistory = props.getBoolean(key); - } - else { - Object value = props.get(key); - if (value == null) { - - } - else if (value.getClass().equals(String.class)) { - params.put(key, (String)value); - } - else if (value.getClass().equals(Boolean.class)) { - params.put(key, (Boolean)value); - } - else if (value.getClass().equals(Integer.class)) { - params.put(key, (Integer)value); - } - } - } - } - - // If wait property, then delay loading - - if (wait > 0) { - try { - synchronized(this) { - this.wait(wait); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - this.webView.showWebPage(url, openExternal, clearHistory, params); - } - - /** - * Clear page history for the app. - */ - public void clearHistory() { - this.webView.clearHistory(); - } - - /** - * Go to previous page displayed. - * This is the same as pressing the backbutton on Android device. - */ - public void backHistory() { - cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - webView.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. - * - * @param override T=override, F=cancel override - */ - public void overrideBackbutton(boolean override) { - LOG.i("App", "WARNING: Back Button Default Behaviour will be overridden. The backbutton event will be fired!"); - webView.bindButton(override); - } - - /** - * Override the default behavior of the Android volume buttons. - * If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired. - * - * @param button volumeup, volumedown - * @param override T=override, F=cancel override - */ - public void overrideButton(String button, boolean override) { - LOG.i("DroidGap", "WARNING: Volume Button Default Behaviour will be overridden. The volume event will be fired!"); - webView.bindButton(button, override); - } - - /** - * Return whether the Android back button is overridden by the user. - * - * @return boolean - */ - public boolean isBackbuttonOverridden() { - return webView.isBackButtonBound(); - } - - /** - * Exit the Android application. - */ - public void exitApp() { - this.webView.postMessage("exit", null); - } - -} diff --git a/framework/src/org/apache/cordova/AudioHandler.java b/framework/src/org/apache/cordova/AudioHandler.java deleted file mode 100644 index 20c3c4ef..00000000 --- a/framework/src/org/apache/cordova/AudioHandler.java +++ /dev/null @@ -1,362 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.DataResource; -import android.content.Context; -import android.media.AudioManager; - -import java.util.ArrayList; - -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import java.util.HashMap; - -/** - * This class called by CordovaActivity to play and record audio. - * The file can be local or over a network using http. - * - * Audio formats supported (tested): - * .mp3, .wav - * - * 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 - */ -public class AudioHandler extends CordovaPlugin { - - public static String TAG = "AudioHandler"; - 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.pausedForPhone = new ArrayList(); - } - - public String getFilePath(String url, String source){ - DataResource dataResource = DataResource.initiateNewDataRequestForUri(url, this.webView.pluginManager, cordova, source); - return dataResource.getRealFile().getPath(); - } - - /** - * Executes the request and returns PluginResult. - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackContext The callback context used when calling back into JavaScript. - * @return A PluginResult object with a status and message. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - PluginResult.Status status = PluginResult.Status.OK; - String result = ""; - - if (action.equals("startRecordingAudio")) { - this.startRecordingAudio(args.getString(0), getFilePath(args.getString(1), "AudioHandler.startRecordingAudio")); - } - else if (action.equals("stopRecordingAudio")) { - this.stopRecordingAudio(args.getString(0)); - } - else if (action.equals("startPlayingAudio")) { - this.startPlayingAudio(args.getString(0), getFilePath(args.getString(1), "AudioHandler.startPlayingAudio")); - } - else if (action.equals("seekToAudio")) { - this.seekToAudio(args.getString(0), args.getInt(1)); - } - else if (action.equals("pausePlayingAudio")) { - this.pausePlayingAudio(args.getString(0)); - } - else if (action.equals("stopPlayingAudio")) { - this.stopPlayingAudio(args.getString(0)); - } else if (action.equals("setVolume")) { - try { - this.setVolume(args.getString(0), Float.parseFloat(args.getString(1))); - } catch (NumberFormatException nfe) { - //no-op - } - } else if (action.equals("getCurrentPositionAudio")) { - float f = this.getCurrentPositionAudio(args.getString(0)); - callbackContext.sendPluginResult(new PluginResult(status, f)); - return true; - } - else if (action.equals("getDurationAudio")) { - float f = this.getDurationAudio(args.getString(0), args.getString(1)); - callbackContext.sendPluginResult(new PluginResult(status, f)); - return true; - } - else if (action.equals("create")) { - String id = args.getString(0); - String src = getFilePath(args.getString(1), "AudioHandler.create"); - AudioPlayer audio = new AudioPlayer(this, id, src); - this.players.put(id, audio); - } - else if (action.equals("release")) { - boolean b = this.release(args.getString(0)); - callbackContext.sendPluginResult(new PluginResult(status, b)); - return true; - } - else { // Unrecognized action. - return false; - } - - callbackContext.sendPluginResult(new PluginResult(status, result)); - - return true; - } - - /** - * Stop all audio players and recorders. - */ - public void onDestroy() { - for (AudioPlayer audio : this.players.values()) { - audio.destroy(); - } - this.players.clear(); - } - - /** - * Stop all audio players and recorders on navigate. - */ - @Override - public void onReset() { - onDestroy(); - } - - /** - * Called when a message is sent to plugin. - * - * @param id The message id - * @param data The message data - * @return Object to stop propagation or null - */ - public Object onMessage(String id, Object data) { - - // If phone message - if (id.equals("telephone")) { - - // If phone ringing, then pause playing - if ("ringing".equals(data) || "offhook".equals(data)) { - - // Get all audio players and pause them - for (AudioPlayer audio : this.players.values()) { - if (audio.getState() == AudioPlayer.STATE.MEDIA_RUNNING.ordinal()) { - this.pausedForPhone.add(audio); - audio.pausePlaying(); - } - } - - } - - // If phone idle, then resume playing those players we paused - else if ("idle".equals(data)) { - for (AudioPlayer audio : this.pausedForPhone) { - audio.startPlaying(null); - } - this.pausedForPhone.clear(); - } - } - return null; - } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - /** - * Release the audio player instance to save memory. - * @param id The id of the audio player - */ - private boolean release(String id) { - if (!this.players.containsKey(id)) { - return false; - } - AudioPlayer audio = this.players.get(id); - this.players.remove(id); - audio.destroy(); - return true; - } - - /** - * Start recording and save the specified file. - * @param id The id of the audio player - * @param file The name of the file - */ - public void startRecordingAudio(String id, String file) { - AudioPlayer audio = this.players.get(id); - if ( audio == null) { - audio = new AudioPlayer(this, id, file); - this.players.put(id, audio); - } - audio.startRecording(file); - } - - /** - * Stop recording and save to the file specified when recording started. - * @param id The id of the audio player - */ - public void stopRecordingAudio(String id) { - AudioPlayer audio = this.players.get(id); - if (audio != null) { - audio.stopRecording(); - } - } - - /** - * Start or resume playing audio file. - * @param id The id of the audio player - * @param file The name of the audio file. - */ - public void startPlayingAudio(String id, String file) { - AudioPlayer audio = this.players.get(id); - if (audio == null) { - audio = new AudioPlayer(this, id, file); - this.players.put(id, audio); - } - audio.startPlaying(file); - } - - /** - * Seek to a location. - * @param id The id of the audio player - * @param milliseconds int: number of milliseconds to skip 1000 = 1 second - */ - public void seekToAudio(String id, int milliseconds) { - AudioPlayer audio = this.players.get(id); - if (audio != null) { - audio.seekToPlaying(milliseconds); - } - } - - /** - * Pause playing. - * @param id The id of the audio player - */ - public void pausePlayingAudio(String id) { - AudioPlayer audio = this.players.get(id); - if (audio != null) { - audio.pausePlaying(); - } - } - - /** - * Stop playing the audio file. - * @param id The id of the audio player - */ - public void stopPlayingAudio(String id) { - AudioPlayer audio = this.players.get(id); - if (audio != null) { - audio.stopPlaying(); - //audio.destroy(); - //this.players.remove(id); - } - } - - /** - * 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 -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. - */ - public float getDurationAudio(String id, String file) { - - // Get audio file - AudioPlayer audio = this.players.get(id); - if (audio != null) { - return (audio.getDuration(file)); - } - - // If not already open, then open the file - else { - audio = new AudioPlayer(this, id, file); - this.players.put(id, audio); - return (audio.getDuration(file)); - } - } - - /** - * Set the audio device to be used for playback. - * - * @param output 1=earpiece, 2=speaker - */ - @SuppressWarnings("deprecation") - public void setAudioOutputDevice(int output) { - AudioManager audiMgr = (AudioManager) this.cordova.getActivity().getSystemService(Context.AUDIO_SERVICE); - if (output == 2) { - audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_SPEAKER, AudioManager.ROUTE_ALL); - } - else if (output == 1) { - audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_EARPIECE, AudioManager.ROUTE_ALL); - } - else { - System.out.println("AudioHandler.setAudioOutputDevice() Error: Unknown output device."); - } - } - - /** - * Get the audio device to be used for playback. - * - * @return 1=earpiece, 2=speaker - */ - @SuppressWarnings("deprecation") - public int getAudioOutputDevice() { - AudioManager audiMgr = (AudioManager) this.cordova.getActivity().getSystemService(Context.AUDIO_SERVICE); - if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_EARPIECE) { - return 1; - } - else if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_SPEAKER) { - return 2; - } - else { - return -1; - } - } - - /** - * Set the volume for an audio device - * - * @param id The id of the audio player - * @param volume Volume to adjust to 0.0f - 1.0f - */ - public void setVolume(String id, float volume) { - AudioPlayer audio = this.players.get(id); - if (audio != null) { - audio.setVolume(volume); - } else { - System.out.println("AudioHandler.setVolume() Error: Unknown Audio Player " + id); - } - } -} diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java deleted file mode 100644 index 01707280..00000000 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ /dev/null @@ -1,553 +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 android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.media.MediaPlayer.OnErrorListener; -import android.media.MediaPlayer.OnPreparedListener; -import android.media.MediaRecorder; -import android.os.Environment; -import android.util.Log; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -/** - * This class implements the audio playback and recording capabilities used by Cordova. - * It is called by the AudioHandler Cordova class. - * 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 - */ -public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener { - - // AudioPlayer modes - public enum MODE { NONE, PLAY, RECORD }; - - // AudioPlayer states - public enum STATE { MEDIA_NONE, - MEDIA_STARTING, - MEDIA_RUNNING, - MEDIA_PAUSED, - MEDIA_STOPPED, - MEDIA_LOADING - }; - - private static final String LOG_TAG = "AudioPlayer"; - - // AudioPlayer message ids - private static int MEDIA_STATE = 1; - private static int MEDIA_DURATION = 2; - private static int MEDIA_POSITION = 3; - private static int MEDIA_ERROR = 9; - - // Media error codes - private static int MEDIA_ERR_NONE_ACTIVE = 0; - private static int MEDIA_ERR_ABORTED = 1; - 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 MODE mode = MODE.NONE; // Playback or Recording mode - private STATE state = 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 player = null; // Audio player object - private boolean prepareOnly = true; // playback after file prepare flag - private int seekOnPrepared = 0; // seek to this location once media is prepared - - /** - * Constructor. - * - * @param handler The audio handler object - * @param id The id of this audio player - */ - public AudioPlayer(AudioHandler handler, String id, String file) { - this.handler = handler; - this.id = id; - this.audioFile = file; - this.recorder = new MediaRecorder(); - - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.3gp"; - } else { - this.tempFile = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording.3gp"; - } - - } - - /** - * Destroy player and stop audio playing or recording. - */ - public void destroy() { - // Stop any play or record - if (this.player != null) { - if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { - this.player.stop(); - this.setState(STATE.MEDIA_STOPPED); - } - this.player.release(); - this.player = null; - } - if (this.recorder != null) { - this.stopRecording(); - this.recorder.release(); - this.recorder = null; - } - } - - /** - * Start recording the specified file. - * - * @param file The name of the file - */ - public void startRecording(String file) { - switch (this.mode) { - case PLAY: - Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode."); - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); - break; - case NONE: - this.audioFile = file; - this.recorder.setAudioSource(MediaRecorder.AudioSource.MIC); - this.recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); // THREE_GPP); - this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); //AMR_NB); - this.recorder.setOutputFile(this.tempFile); - try { - this.recorder.prepare(); - this.recorder.start(); - this.setState(STATE.MEDIA_RUNNING); - return; - } catch (IllegalStateException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); - break; - case RECORD: - Log.d(LOG_TAG, "AudioPlayer Error: Already recording."); - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); - } - } - - /** - * Save temporary recorded file to specified name - * - * @param file - */ - public void moveFile(String file) { - /* this is a hack to save the file as the specified name */ - File f = new File(this.tempFile); - - if (!file.startsWith("/")) { - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - file = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + file; - } else { - file = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/" + file; - } - } - - String logMsg = "renaming " + this.tempFile + " to " + file; - Log.d(LOG_TAG, logMsg); - if (!f.renameTo(new File(file))) Log.e(LOG_TAG, "FAILED " + logMsg); - } - - /** - * Stop recording and save to the file specified when recording started. - */ - public void stopRecording() { - if (this.recorder != null) { - try{ - if (this.state == STATE.MEDIA_RUNNING) { - this.recorder.stop(); - this.setState(STATE.MEDIA_STOPPED); - } - this.recorder.reset(); - this.moveFile(this.audioFile); - } - catch (Exception e) { - e.printStackTrace(); - } - } - } - - //========================================================================== - // Playback - //========================================================================== - - /** - * Start or resume playing audio file. - * - * @param file The name of the audio file. - */ - public void startPlaying(String file) { - if (this.readyPlayer(file) && this.player != null) { - this.player.start(); - this.setState(STATE.MEDIA_RUNNING); - this.seekOnPrepared = 0; //insures this is always reset - } else { - this.prepareOnly = false; - } - } - - /** - * Seek or jump to a new time in the track. - */ - public void seekToPlaying(int milliseconds) { - if (this.readyPlayer(this.audioFile)) { - this.player.seekTo(milliseconds); - Log.d(LOG_TAG, "Send a onStatus update for the new seek"); - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");"); - } - else { - this.seekOnPrepared = milliseconds; - } - } - - /** - * Pause playing. - */ - public void pausePlaying() { - - // If playing, then pause - if (this.state == STATE.MEDIA_RUNNING && this.player != null) { - this.player.pause(); - this.setState(STATE.MEDIA_PAUSED); - } - else { - Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal()); - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});"); - } - } - - /** - * Stop playing the audio file. - */ - public void stopPlaying() { - if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { - this.player.pause(); - this.player.seekTo(0); - Log.d(LOG_TAG, "stopPlaying is calling stopped"); - this.setState(STATE.MEDIA_STOPPED); - } - else { - Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal()); - this.handler.webView.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 player The MediaPlayer that reached the end of the file - */ - public void onCompletion(MediaPlayer player) { - Log.d(LOG_TAG, "on completion is calling stopped"); - this.setState(STATE.MEDIA_STOPPED); - } - - /** - * Get current position of playback. - * - * @return position in msec or -1 if not playing - */ - public long getCurrentPosition() { - if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { - int curPos = this.player.getCurrentPosition(); - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");"); - return curPos; - } - else { - return -1; - } - } - - /** - * 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 - */ - public boolean isStreaming(String file) { - if (file.contains("http://") || file.contains("https://")) { - return true; - } - else { - return false; - } - } - - /** - * 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 - } - - // If audio file already loaded and started, then return duration - if (this.player != null) { - return this.duration; - } - - // If no player yet, then create one - else { - this.prepareOnly = true; - this.startPlaying(file); - - // This will only return value for local, since streaming - // file hasn't been read yet. - return this.duration; - } - } - - /** - * Callback to be invoked when the media source is ready for playback. - * - * @param player The MediaPlayer that is ready for playback - */ - public void onPrepared(MediaPlayer player) { - // Listen for playback completion - this.player.setOnCompletionListener(this); - // seek to any location received while not prepared - this.seekToPlaying(this.seekOnPrepared); - // If start playing after prepared - if (!this.prepareOnly) { - this.player.start(); - this.setState(STATE.MEDIA_RUNNING); - this.seekOnPrepared = 0; //reset only when played - } else { - this.setState(STATE.MEDIA_STARTING); - } - // Save off duration - this.duration = getDurationInSeconds(); - // reset prepare only flag - this.prepareOnly = true; - - // Send status notification to JavaScript - this.handler.webView.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() { - return (this.player.getDuration() / 1000.0f); - } - - /** - * Callback to be invoked when there has been an error during an asynchronous operation - * (other errors will throw exceptions at method call time). - * - * @param player 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 player, int arg1, int arg2) { - Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2 + ")"); - - // TODO: Not sure if this needs to be sent? - this.player.stop(); - this.player.release(); - - // Send error notification to JavaScript - this.handler.webView.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(STATE state) { - if (this.state != state) { - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state.ordinal() + ");"); - } - this.state = state; - } - - /** - * Set the mode and send it to JavaScript. - * - * @param state - */ - private void setMode(MODE mode) { - if (this.mode != mode) { - //mode is not part of the expected behavior, so no notification - //this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");"); - } - this.mode = mode; - } - - /** - * Get the audio state. - * - * @return int - */ - public int getState() { - return this.state.ordinal(); - } - - /** - * Set the volume for audio player - * - * @param volume - */ - public void setVolume(float volume) { - this.player.setVolume(volume, volume); - } - - /** - * attempts to put the player in play mode - * @return true if in playmode, false otherwise - */ - private boolean playMode() { - switch(this.mode) { - case NONE: - this.setMode(MODE.PLAY); - break; - case PLAY: - break; - case RECORD: - Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode."); - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - return false; //player is not ready - } - return true; - } - - /** - * attempts to initialize the media player for playback - * @param file the file to play - * @return false if player not ready, reports if in wrong mode or state - */ - private boolean readyPlayer(String file) { - if (playMode()) { - switch (this.state) { - case MEDIA_NONE: - if (this.player == null) { - this.player = new MediaPlayer(); - } - try { - this.loadAudioFile(file); - } catch (Exception e) { - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});"); - } - return false; - case MEDIA_LOADING: - //cordova js is not aware of MEDIA_LOADING, so we send MEDIA_STARTING instead - Log.d(LOG_TAG, "AudioPlayer Loading: startPlaying() called during media preparation: " + STATE.MEDIA_STARTING.ordinal()); - this.prepareOnly = false; - return false; - case MEDIA_STARTING: - case MEDIA_RUNNING: - case MEDIA_PAUSED: - return true; - case MEDIA_STOPPED: - //if we are readying the same file - if (this.audioFile.compareTo(file) == 0) { - //reset the audio file - player.seekTo(0); - player.pause(); - return true; - } else { - //reset the player - this.player.reset(); - try { - this.loadAudioFile(file); - } catch (Exception e) { - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } - //if we had to prepare= the file, we won't be in the correct state for playback - return false; - } - default: - Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state); - this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});"); - } - } - return false; - } - - /** - * load audio file - * @throws IOException - * @throws IllegalStateException - * @throws SecurityException - * @throws IllegalArgumentException - */ - private void loadAudioFile(String file) throws IllegalArgumentException, SecurityException, IllegalStateException, IOException { - if (this.isStreaming(file)) { - this.player.setDataSource(file); - this.player.setAudioStreamType(AudioManager.STREAM_MUSIC); - //if it's a streaming file, play mode is implied - this.setMode(MODE.PLAY); - this.setState(STATE.MEDIA_STARTING); - this.player.setOnPreparedListener(this); - this.player.prepareAsync(); - } - else { - if (file.startsWith("/android_asset/")) { - String f = file.substring(15); - android.content.res.AssetFileDescriptor fd = this.handler.cordova.getActivity().getAssets().openFd(f); - this.player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); - } - else { - File fp = new File(file); - if (fp.exists()) { - FileInputStream fileInputStream = new FileInputStream(file); - this.player.setDataSource(fileInputStream.getFD()); - } - else { - this.player.setDataSource("/sdcard/" + file); - } - } - this.setState(STATE.MEDIA_STARTING); - this.player.setOnPreparedListener(this); - this.player.prepare(); - - // Get duration - this.duration = getDurationInSeconds(); - } - } -} diff --git a/framework/src/org/apache/cordova/BatteryListener.java b/framework/src/org/apache/cordova/BatteryListener.java deleted file mode 100755 index 85558c2a..00000000 --- a/framework/src/org/apache/cordova/BatteryListener.java +++ /dev/null @@ -1,163 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.Log; - -public class BatteryListener extends CordovaPlugin { - - private static final String LOG_TAG = "BatteryManager"; - - BroadcastReceiver receiver; - - private CallbackContext batteryCallbackContext = null; - - /** - * Constructor. - */ - public BatteryListener() { - this.receiver = null; - } - - /** - * Executes the request. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackContext The callback context used when calling back into JavaScript. - * @return True if the action was valid, false if not. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) { - if (action.equals("start")) { - if (this.batteryCallbackContext != null) { - callbackContext.error( "Battery listener already running."); - return true; - } - this.batteryCallbackContext = callbackContext; - - // We need to listen to power events to update battery status - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); - if (this.receiver == null) { - this.receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - updateBatteryInfo(intent); - } - }; - cordova.getActivity().registerReceiver(this.receiver, intentFilter); - } - - // Don't return any result now, since status results will be sent when events come in from broadcast receiver - PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT); - pluginResult.setKeepCallback(true); - callbackContext.sendPluginResult(pluginResult); - return true; - } - - else if (action.equals("stop")) { - removeBatteryListener(); - this.sendUpdate(new JSONObject(), false); // release status callback in JS side - this.batteryCallbackContext = null; - callbackContext.success(); - return true; - } - - return false; - } - - /** - * Stop battery receiver. - */ - public void onDestroy() { - removeBatteryListener(); - } - - /** - * Stop battery receiver. - */ - public void onReset() { - removeBatteryListener(); - } - - /** - * Stop the battery receiver and set it to null. - */ - private void removeBatteryListener() { - if (this.receiver != null) { - try { - this.cordova.getActivity().unregisterReceiver(this.receiver); - this.receiver = null; - } catch (Exception e) { - Log.e(LOG_TAG, "Error unregistering battery receiver: " + e.getMessage(), e); - } - } - } - - /** - * Creates a JSONObject with the current battery information - * - * @param batteryIntent the current battery information - * @return a JSONObject containing the battery status information - */ - private JSONObject getBatteryInfo(Intent batteryIntent) { - JSONObject obj = new JSONObject(); - try { - obj.put("level", batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, 0)); - obj.put("isPlugged", batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_PLUGGED, -1) > 0 ? true : false); - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - return obj; - } - - /** - * Updates the JavaScript side whenever the battery changes - * - * @param batteryIntent the current battery information - * @return - */ - private void updateBatteryInfo(Intent batteryIntent) { - sendUpdate(this.getBatteryInfo(batteryIntent), true); - } - - /** - * Create a new plugin result and send it back to JavaScript - * - * @param connection the network info to set as navigator.connection - */ - private void sendUpdate(JSONObject info, boolean keepCallback) { - if (this.batteryCallbackContext != null) { - PluginResult result = new PluginResult(PluginResult.Status.OK, info); - result.setKeepCallback(keepCallback); - this.batteryCallbackContext.sendPluginResult(result); - } - } -} diff --git a/framework/src/org/apache/cordova/CameraLauncher.java b/framework/src/org/apache/cordova/CameraLauncher.java deleted file mode 100755 index 1974dd71..00000000 --- a/framework/src/org/apache/cordova/CameraLauncher.java +++ /dev/null @@ -1,824 +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.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.apache.commons.codec.binary.Base64; -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.DataResource; -import org.apache.cordova.api.LOG; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; - -import android.app.Activity; -import android.content.ContentValues; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.graphics.Bitmap.CompressFormat; -import android.media.MediaScannerConnection; -import android.media.MediaScannerConnection.MediaScannerConnectionClient; -import android.net.Uri; -import android.os.Environment; -import android.provider.MediaStore; -import android.util.Log; - -/** - * This class launches the camera view, allows the user to take a picture, closes the camera view, - * and returns the captured image. When the camera view is closed, the screen displayed before - * the camera view was shown is redisplayed. - */ -public class CameraLauncher extends CordovaPlugin implements MediaScannerConnectionClient { - - private static final int DATA_URL = 0; // Return base64 encoded string - private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android) - private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI - - private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) - private static final int CAMERA = 1; // Take picture from camera - private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture library (same as PHOTOLIBRARY for Android) - - private static final int PICTURE = 0; // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType - private static final int VIDEO = 1; // allow selection of video only, ONLY RETURNS URL - private static final int ALLMEDIA = 2; // allow selection from all media types - - private static final int JPEG = 0; // Take a picture of type JPEG - private static final int PNG = 1; // Take a picture of type PNG - private static final String GET_PICTURE = "Get Picture"; - private static final String GET_VIDEO = "Get Video"; - private static final String GET_All = "Get All"; - - private static final String LOG_TAG = "CameraLauncher"; - - private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - private int targetWidth; // desired width of the image - private int targetHeight; // desired height of the image - private Uri imageUri; // Uri of captured image - private int encodingType; // Type of encoding to use - private int mediaType; // What type of media to retrieve - private boolean saveToPhotoAlbum; // Should the picture be saved to the device's photo album - private boolean correctOrientation; // Should the pictures orientation be corrected - //private boolean allowEdit; // Should we allow the user to crop the image. UNUSED. - - public CallbackContext callbackContext; - private int numPics; - - private MediaScannerConnection conn; // Used to update gallery app with newly-written files - private Uri scanMe; // Uri of image to be added to content store - - //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. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackContext The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - this.callbackContext = callbackContext; - - if (action.equals("takePicture")) { - int srcType = CAMERA; - int destType = FILE_URI; - this.saveToPhotoAlbum = false; - this.targetHeight = 0; - this.targetWidth = 0; - this.encodingType = JPEG; - this.mediaType = PICTURE; - this.mQuality = 80; - - this.mQuality = args.getInt(0); - destType = args.getInt(1); - srcType = args.getInt(2); - this.targetWidth = args.getInt(3); - this.targetHeight = args.getInt(4); - this.encodingType = args.getInt(5); - this.mediaType = args.getInt(6); - //this.allowEdit = args.getBoolean(7); // This field is unused. - this.correctOrientation = args.getBoolean(8); - this.saveToPhotoAlbum = args.getBoolean(9); - - // If the user specifies a 0 or smaller width/height - // make it -1 so later comparisons succeed - if (this.targetWidth < 1) { - this.targetWidth = -1; - } - if (this.targetHeight < 1) { - this.targetHeight = -1; - } - - if (srcType == CAMERA) { - this.takePicture(destType, encodingType); - } - else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { - this.getImage(srcType, destType); - } - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - callbackContext.sendPluginResult(r); - return true; - } - return false; - } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - /** - * Take a picture with the camera. - * When an image is captured or the camera view is cancelled, the result is returned - * in CordovaActivity.onActivityResult, which forwards the result to this.onActivityResult. - * - * The image can either be returned as a base64 string or a URI that points to the file. - * To display base64 string in an img tag, set the source to: - * img.src="data:image/jpeg;base64,"+result; - * or to display URI in an img tag - * img.src=result; - * - * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - * @param returnType Set the type of image to return. - */ - public void takePicture(int returnType, int encodingType) { - // Save the number of images currently on disk for later - this.numPics = queryImgDB(whichContentStore()).getCount(); - - // Display camera - Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); - - // Specify file so that large image is captured and returned - File photo = createCaptureFile(encodingType); - intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); - this.imageUri = Uri.fromFile(photo); - - if (this.cordova != null) { - this.cordova.startActivityForResult((CordovaPlugin) 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"); - } - - /** - * Create a file in the applications temporary directory based upon the supplied encoding. - * - * @param encodingType of the image to be taken - * @return a File object pointing to the temporary picture - */ - private File createCaptureFile(int encodingType) { - File photo = null; - if (encodingType == JPEG) { - photo = new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), ".Pic.jpg"); - } else if (encodingType == PNG) { - photo = new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), ".Pic.png"); - } else { - throw new IllegalArgumentException("Invalid Encoding Type: " + encodingType); - } - return photo; - } - - /** - * Get image from photo library. - * - * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - * @param srcType The album to get image from. - * @param returnType Set the type of image to return. - */ - // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! - public void getImage(int srcType, int returnType) { - Intent intent = new Intent(); - String title = GET_PICTURE; - if (this.mediaType == PICTURE) { - intent.setType("image/*"); - } - else if (this.mediaType == VIDEO) { - intent.setType("video/*"); - title = GET_VIDEO; - } - else if (this.mediaType == ALLMEDIA) { - // I wanted to make the type 'image/*, video/*' but this does not work on all versions - // of android so I had to go with the wildcard search. - intent.setType("*/*"); - title = GET_All; - } - - intent.setAction(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - if (this.cordova != null) { - this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent, - new String(title)), (srcType + 1) * 16 + returnType + 1); - } - } - - /** - * Called when the camera view exits. - * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). - */ - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - - // Get src and dest types from request code - int srcType = (requestCode / 16) - 1; - int destType = (requestCode % 16) - 1; - int rotate = 0; - - // If CAMERA - if (srcType == CAMERA) { - // If image available - if (resultCode == Activity.RESULT_OK) { - try { - // Create an ExifHelper to save the exif data that is lost during compression - ExifHelper exif = new ExifHelper(); - try { - if (this.encodingType == JPEG) { - exif.createInFile(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/.Pic.jpg"); - exif.readExifData(); - rotate = exif.getOrientation(); - } - } catch (IOException e) { - e.printStackTrace(); - } - - Bitmap bitmap = null; - Uri uri = null; - - // If sending base64 image back - if (destType == DATA_URL) { - bitmap = getScaledBitmap(imageUri.toString()); - if (bitmap == null) { - // Try to get the bitmap from intent. - bitmap = (Bitmap)intent.getExtras().get("data"); - } - - // Double-check the bitmap. - if (bitmap == null) { - Log.d(LOG_TAG, "I either have a null image path or bitmap"); - this.failPicture("Unable to create bitmap!"); - return; - } - - if (rotate != 0 && this.correctOrientation) { - bitmap = getRotatedBitmap(rotate, bitmap, exif); - } - - this.processPicture(bitmap); - checkForDuplicateImage(DATA_URL); - } - - // If sending filename back - else if (destType == FILE_URI || destType == NATIVE_URI) { - if (this.saveToPhotoAlbum) { - Uri inputUri = getUriFromMediaStore(); - //Just because we have a media URI doesn't mean we have a real file, we need to make it - DataResource dataResource = DataResource.initiateNewDataRequestForUri(inputUri, webView.pluginManager, cordova, "CameraLauncher.CameraExitIntent"); - File file = dataResource.getRealFile(); - uri = Uri.fromFile(file); - } else { - uri = Uri.fromFile(new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), System.currentTimeMillis() + ".jpg")); - } - - if (uri == null) { - this.failPicture("Error capturing image - no media storage found."); - } - - // If all this is true we shouldn't compress the image. - if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 && - !this.correctOrientation) { - writeUncompressedImage(uri); - - this.callbackContext.success(uri.toString()); - } else { - bitmap = getScaledBitmap(imageUri.toString()); - - if (rotate != 0 && this.correctOrientation) { - bitmap = getRotatedBitmap(rotate, bitmap, exif); - } - - // Add compressed version of captured image to returned media store Uri - DataResource dataResource = DataResource.initiateNewDataRequestForUri(uri, webView.pluginManager, cordova, "CameraLauncher.CameraExitIntent"); - OutputStream os = dataResource.getOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); - os.close(); - - // Restore exif data to file - if (this.encodingType == JPEG) { - String exifPath; - if (this.saveToPhotoAlbum) { - exifPath = dataResource.getRealFile().getPath(); - } else { - exifPath = uri.getPath(); - } - exif.createOutFile(exifPath); - exif.writeExifData(); - } - - } - // Send Uri back to JavaScript for viewing image - this.callbackContext.success(uri.toString()); - } - - this.cleanup(FILE_URI, this.imageUri, uri, bitmap); - bitmap = null; - - } catch (IOException e) { - e.printStackTrace(); - this.failPicture("Error capturing image."); - } - } - - // If cancelled - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Camera cancelled."); - } - - // If something else - else { - this.failPicture("Did not complete!"); - } - } - - // If retrieving photo from library - else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { - if (resultCode == Activity.RESULT_OK) { - Uri uri = intent.getData(); - - // 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.callbackContext.success(uri.toString()); - } - else { - // This is a special case to just return the path as no scaling, - // rotating, nor compressing needs to be done - if (this.targetHeight == -1 && this.targetWidth == -1 && - (destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) { - this.callbackContext.success(uri.toString()); - } else { - String uriString = uri.toString(); - DataResource dataResource = DataResource.initiateNewDataRequestForUri(uri, webView.pluginManager, cordova, "CameraLauncher.CameraExitIntent"); - // Get the path to the image. Makes loading so much easier. - String mimeType = dataResource.getMimeType(); - // If we don't have a valid image so quit. - if (!("image/jpeg".equalsIgnoreCase(mimeType) || "image/png".equalsIgnoreCase(mimeType))) { - Log.d(LOG_TAG, "I either have a null image path or bitmap"); - this.failPicture("Unable to retrieve path to picture!"); - return; - } - Bitmap bitmap = null; - try { - bitmap = getScaledBitmap(uriString); - } catch (IOException e) { - e.printStackTrace(); - } - if (bitmap == null) { - Log.d(LOG_TAG, "I either have a null image path or bitmap"); - this.failPicture("Unable to create bitmap!"); - return; - } - - if (this.correctOrientation) { - rotate = getImageOrientation(uri); - if (rotate != 0) { - Matrix matrix = new Matrix(); - matrix.setRotate(rotate); - bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - } - } - - // If sending base64 image back - if (destType == DATA_URL) { - this.processPicture(bitmap); - } - - // If sending filename back - else if (destType == FILE_URI || destType == NATIVE_URI) { - // Do we need to scale the returned file - if (this.targetHeight > 0 && this.targetWidth > 0) { - try { - // Create an ExifHelper to save the exif data that is lost during compression - String resizePath = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg"; - // Some content: URIs do not map to file paths (e.g. picasa). - File realFile = DataResource.initiateNewDataRequestForUri(uri, webView.pluginManager, cordova, "CameraLauncher.CameraExitIntent").getRealFile(); - String realPath = realFile != null? realFile.getPath() : null; - ExifHelper exif = new ExifHelper(); - if (realPath != null && this.encodingType == JPEG) { - try { - exif.createInFile(realPath); - exif.readExifData(); - rotate = exif.getOrientation(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - OutputStream os = new FileOutputStream(resizePath); - bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); - os.close(); - - // Restore exif data to file - if (realPath != null && this.encodingType == JPEG) { - exif.createOutFile(resizePath); - exif.writeExifData(); - } - - // 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.callbackContext.success("file://" + resizePath + "?" + System.currentTimeMillis()); - } catch (Exception e) { - e.printStackTrace(); - this.failPicture("Error retrieving image."); - } - } - else { - this.callbackContext.success(uri.toString()); - } - } - if (bitmap != null) { - bitmap.recycle(); - bitmap = null; - } - System.gc(); - } - } - } - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Selection cancelled."); - } - else { - this.failPicture("Selection did not complete!"); - } - } - } - - private int getImageOrientation(Uri uri) { - String[] cols = { MediaStore.Images.Media.ORIENTATION }; - Cursor cursor = cordova.getActivity().getContentResolver().query(uri, - cols, null, null, null); - int rotate = 0; - if (cursor != null) { - cursor.moveToPosition(0); - rotate = cursor.getInt(0); - cursor.close(); - } - return rotate; - } - - /** - * Figure out if the bitmap should be rotated. For instance if the picture was taken in - * portrait mode - * - * @param rotate - * @param bitmap - * @return rotated bitmap - */ - private Bitmap getRotatedBitmap(int rotate, Bitmap bitmap, ExifHelper exif) { - Matrix matrix = new Matrix(); - if (rotate == 180) { - matrix.setRotate(rotate); - } else { - matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2); - } - bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - exif.resetOrientation(); - return bitmap; - } - - /** - * In the special case where the default width, height and quality are unchanged - * we just write the file out to disk saving the expensive Bitmap.compress function. - * - * @param uri - * @throws FileNotFoundException - * @throws IOException - */ - private void writeUncompressedImage(Uri uri) throws FileNotFoundException, - IOException { - DataResource inputDataResource = DataResource.initiateNewDataRequestForUri(imageUri, webView.pluginManager, cordova, "CameraLauncher.writeUncompressedImage"); - InputStream fis = inputDataResource.getInputStream(); - DataResource outDataResource = DataResource.initiateNewDataRequestForUri(uri, webView.pluginManager, cordova, "CameraLauncher.writeUncompressedImage"); - OutputStream os = outDataResource.getOutputStream(); - if(fis == null) { - throw new FileNotFoundException("Could not get the input file"); - } else if(os == null) { - throw new FileNotFoundException("Could not get the output file"); - } - byte[] buffer = new byte[4096]; - int len; - while ((len = fis.read(buffer)) != -1) { - os.write(buffer, 0, len); - } - os.flush(); - os.close(); - fis.close(); - } - - /** - * Create entry in media store for image - * - * @return uri - */ - private Uri getUriFromMediaStore() { - ContentValues values = new ContentValues(); - values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); - Uri uri; - try { - uri = this.cordova.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.cordova.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."); - return null; - } - } - return uri; - } - - /** - * Return a scaled bitmap based on the target width and height - * - * @param imagePath - * @return - * @throws IOException - */ - private Bitmap getScaledBitmap(String imageUrl) throws IOException { - // If no new width or height were specified return the original bitmap - DataResource dataResource = DataResource.initiateNewDataRequestForUri(imageUrl, webView.pluginManager, cordova, "CameraLauncher.getScaledBitmap"); - if (this.targetWidth <= 0 && this.targetHeight <= 0) { - return BitmapFactory.decodeStream(dataResource.getInputStream()); - } - - // figure out the original width and height of the image - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(dataResource.getInputStream(), null, options); - - //CB-2292: WTF? Why is the width null? - if(options.outWidth == 0 || options.outHeight == 0) - { - return null; - } - - // determine the correct aspect ratio - int[] widthHeight = calculateAspectRatio(options.outWidth, options.outHeight); - - // Load in the smallest bitmap possible that is closest to the size we want - options.inJustDecodeBounds = false; - options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight); - Bitmap unscaledBitmap = BitmapFactory.decodeStream(dataResource.getInputStream(), null, options); - if (unscaledBitmap == null) { - return null; - } - - return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true); - } - - /** - * Maintain the aspect ratio so the resulting image does not look smooshed - * - * @param origWidth - * @param origHeight - * @return - */ - public int[] calculateAspectRatio(int origWidth, int origHeight) { - int newWidth = this.targetWidth; - int newHeight = this.targetHeight; - - // If no new width or height were specified return the original bitmap - if (newWidth <= 0 && newHeight <= 0) { - newWidth = origWidth; - newHeight = origHeight; - } - // Only the width was specified - else if (newWidth > 0 && newHeight <= 0) { - newHeight = (newWidth * origHeight) / origWidth; - } - // only the height was specified - else if (newWidth <= 0 && newHeight > 0) { - newWidth = (newHeight * origWidth) / origHeight; - } - // If the user specified both a positive width and height - // (potentially different aspect ratio) then the width or height is - // scaled so that the image fits while maintaining aspect ratio. - // Alternatively, the specified width and height could have been - // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this - // would result in whitespace in the new image. - else { - double newRatio = newWidth / (double) newHeight; - double origRatio = origWidth / (double) origHeight; - - if (origRatio > newRatio) { - newHeight = (newWidth * origHeight) / origWidth; - } else if (origRatio < newRatio) { - newWidth = (newHeight * origWidth) / origHeight; - } - } - - int[] retval = new int[2]; - retval[0] = newWidth; - retval[1] = newHeight; - return retval; - } - - /** - * Figure out what ratio we can load our image into memory at while still being bigger than - * our desired width and height - * - * @param srcWidth - * @param srcHeight - * @param dstWidth - * @param dstHeight - * @return - */ - public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) { - final float srcAspect = (float)srcWidth / (float)srcHeight; - final float dstAspect = (float)dstWidth / (float)dstHeight; - - if (srcAspect > dstAspect) { - return srcWidth / dstWidth; - } else { - return srcHeight / dstHeight; - } - } - - /** - * Creates a cursor that can be used to determine how many images we have. - * - * @return a cursor - */ - private Cursor queryImgDB(Uri contentStore) { - return this.cordova.getActivity().getContentResolver().query( - contentStore, - new String[] { MediaStore.Images.Media._ID }, - null, - null, - null); - } - - /** - * Cleans up after picture taking. Checking for duplicates and that kind of stuff. - * @param newImage - */ - private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) { - if (bitmap != null) { - bitmap.recycle(); - } - - DataResource dataResource = DataResource.initiateNewDataRequestForUri(oldImage, webView.pluginManager, cordova, "CameraLauncher.cleanup"); - File file = dataResource.getRealFile(); - if(file != null) { - // Clean up initial camera-written image file. - file.delete(); - - checkForDuplicateImage(imageType); - // Scan for the gallery to update pic refs in gallery - if (this.saveToPhotoAlbum && newImage != null) { - this.scanForGallery(newImage); - } - - System.gc(); - } - } - - /** - * Used to find out if we are in a situation where the Camera Intent adds to images - * to the content store. If we are using a FILE_URI and the number of images in the DB - * increases by 2 we have a duplicate, when using a DATA_URL the number is 1. - * - * @param type FILE_URI or DATA_URL - */ - private void checkForDuplicateImage(int type) { - int diff = 1; - Uri contentStore = whichContentStore(); - Cursor cursor = queryImgDB(contentStore); - int currentNumOfImages = cursor.getCount(); - - if (type == FILE_URI && this.saveToPhotoAlbum) { - diff = 2; - } - - // delete the duplicate file if the difference is 2 for file URI or 1 for Data URL - if ((currentNumOfImages - numPics) == diff) { - cursor.moveToLast(); - int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))); - if (diff == 2) { - id--; - } - Uri uri = Uri.parse(contentStore + "/" + id); - this.cordova.getActivity().getContentResolver().delete(uri, null, null); - } - } - - /** - * Determine if we are storing the images in internal or external storage - * @return Uri - */ - private Uri whichContentStore() { - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - return android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else { - return android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI; - } - } - - /** - * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript. - * - * @param bitmap - */ - public void processPicture(Bitmap bitmap) { - ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); - try { - if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) { - byte[] code = jpeg_data.toByteArray(); - byte[] output = Base64.encodeBase64(code); - String js_out = new String(output); - this.callbackContext.success(js_out); - js_out = null; - output = null; - code = null; - } - } catch (Exception e) { - this.failPicture("Error compressing image."); - } - jpeg_data = null; - } - - /** - * Send error message to JavaScript. - * - * @param err - */ - public void failPicture(String err) { - this.callbackContext.error(err); - } - - private void scanForGallery(Uri newImage) { - this.scanMe = newImage; - if(this.conn != null) { - this.conn.disconnect(); - } - this.conn = new MediaScannerConnection(this.cordova.getActivity().getApplicationContext(), this); - conn.connect(); - } - - public void onMediaScannerConnected() { - try{ - this.conn.scanFile(this.scanMe.toString(), "image/*"); - } catch (java.lang.IllegalStateException e){ - LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture"); - } - - } - - public void onScanCompleted(String path, Uri uri) { - this.conn.disconnect(); - } -} diff --git a/framework/src/org/apache/cordova/Capture.java b/framework/src/org/apache/cordova/Capture.java deleted file mode 100644 index d7ad419e..00000000 --- a/framework/src/org/apache/cordova/Capture.java +++ /dev/null @@ -1,450 +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.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.OutputStream; - -import android.os.Build; -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.DataResource; -import org.apache.cordova.api.LOG; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.app.Activity; -import android.content.ContentValues; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.BitmapFactory; -import android.media.MediaPlayer; -import android.net.Uri; -import android.os.Environment; -import android.provider.MediaStore; -import android.util.Log; - -public class Capture extends CordovaPlugin { - - private static final String VIDEO_3GPP = "video/3gpp"; - private static final String VIDEO_MP4 = "video/mp4"; - private static final String AUDIO_3GPP = "audio/3gpp"; - private static final String IMAGE_JPEG = "image/jpeg"; - - private static final int CAPTURE_AUDIO = 0; // Constant for capture audio - private static final int CAPTURE_IMAGE = 1; // Constant for capture image - private static final int CAPTURE_VIDEO = 2; // Constant for capture video - private static final String LOG_TAG = "Capture"; - - private 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_NO_MEDIA_FILES = 3; - - private CallbackContext callbackContext; // The callback context from which we were invoked. - private long limit; // the number of pics/vids/clips to take - private double duration; // optional duration parameter for video recording - private JSONArray results; // The array of results to be returned to the user - private int numPics; // Number of pictures before capture activity - - //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 boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - this.callbackContext = callbackContext; - this.limit = 1; - this.duration = 0.0f; - this.results = new JSONArray(); - - JSONObject options = args.optJSONObject(0); - if (options != null) { - limit = options.optLong("limit", 1); - duration = options.optDouble("duration", 0.0f); - } - - if (action.equals("getFormatData")) { - JSONObject obj = getFormatData(args.getString(0), args.getString(1)); - callbackContext.success(obj); - return true; - } - else if (action.equals("captureAudio")) { - this.captureAudio(); - } - else if (action.equals("captureImage")) { - this.captureImage(); - } - else if (action.equals("captureVideo")) { - this.captureVideo(duration); - } - else { - return false; - } - - return true; - } - - /** - * Provides the media data file data depending on it's mime type - * - * @param filePath path to the file - * @param mimeType of the file - * @return a MediaFileData object - */ - private JSONObject getFormatData(String filePath, String mimeType) throws JSONException { - JSONObject obj = new JSONObject(); - // setup defaults - obj.put("height", 0); - obj.put("width", 0); - obj.put("bitrate", 0); - obj.put("duration", 0); - obj.put("codecs", ""); - - // If the mimeType isn't set the rest will fail - // so let's see if we can determine it. - if (mimeType == null || mimeType.equals("") || "null".equals(mimeType)) { - DataResource dataResource = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "Capture.dataResource"); - mimeType = dataResource.getMimeType(); - } - Log.d(LOG_TAG, "Mime type = " + mimeType); - - if (mimeType.equals(IMAGE_JPEG) || filePath.endsWith(".jpg")) { - obj = getImageData(filePath, obj); - } - else if (mimeType.endsWith(AUDIO_3GPP)) { - obj = getAudioVideoData(filePath, obj, false); - } - else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) { - obj = getAudioVideoData(filePath, obj, true); - } - return obj; - } - - /** - * Get the Image specific attributes - * - * @param filePath path to the file - * @param obj represents the Media File Data - * @return a JSONObject that represents the Media File Data - * @throws JSONException - */ - private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - DataResource dataResource = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "Capture.getImageData"); - BitmapFactory.decodeFile(dataResource.getRealFile().getPath(), options); - obj.put("height", options.outHeight); - obj.put("width", options.outWidth); - return obj; - } - - /** - * Get the Image specific attributes - * - * @param filePath path to the file - * @param obj represents the Media File Data - * @param video if true get video attributes as well - * @return a JSONObject that represents the Media File Data - * @throws JSONException - */ - private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException { - MediaPlayer player = new MediaPlayer(); - try { - player.setDataSource(filePath); - player.prepare(); - obj.put("duration", player.getDuration() / 1000); - if (video) { - obj.put("height", player.getVideoHeight()); - obj.put("width", player.getVideoWidth()); - } - } catch (IOException e) { - Log.d(LOG_TAG, "Error: loading video file"); - } - return obj; - } - - /** - * Sets up an intent to capture audio. Result handled by onActivityResult() - */ - private void captureAudio() { - Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); - - this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_AUDIO); - } - - /** - * Sets up an intent to capture images. Result handled by onActivityResult() - */ - private void captureImage() { - // Save the number of images currently on disk for later - this.numPics = queryImgDB(whichContentStore()).getCount(); - - 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(this.cordova.getActivity()), "Capture.jpg"); - intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); - - this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_IMAGE); - } - - /** - * Sets up an intent to capture video. Result handled by onActivityResult() - */ - private void captureVideo(double duration) { - Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); - - if(Build.VERSION.SDK_INT > 8){ - intent.putExtra("android.intent.extra.durationLimit", duration); - } - this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_VIDEO); - } - - /** - * Called when the video view exits. - * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). - * @throws JSONException - */ - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - - // Result received okay - if (resultCode == Activity.RESULT_OK) { - // An audio clip was requested - if (requestCode == CAPTURE_AUDIO) { - // Get the uri of the audio clip - Uri data = intent.getData(); - // create a file object from the uri - results.put(createMediaFile(data)); - - if (results.length() >= limit) { - // Send Uri back to JavaScript for listening to audio - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results)); - } else { - // still need to capture more audio clips - captureAudio(); - } - } else if (requestCode == CAPTURE_IMAGE) { - // For some reason if I try to do: - // Uri data = intent.getData(); - // It crashes in the emulator and on my phone with a null pointer exception - // To work around it I had to grab the code from CameraLauncher.java - try { - // Create 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.cordova.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.cordova.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.")); - return; - } - } - FileInputStream fis = new FileInputStream(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/Capture.jpg"); - OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri); - byte[] buffer = new byte[4096]; - int len; - while ((len = fis.read(buffer)) != -1) { - os.write(buffer, 0, len); - } - os.flush(); - os.close(); - fis.close(); - - // Add image to results - results.put(createMediaFile(uri)); - - checkForDuplicateImage(); - - if (results.length() >= limit) { - // Send Uri back to JavaScript for viewing image - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results)); - } else { - // still need to capture more images - captureImage(); - } - } catch (IOException e) { - e.printStackTrace(); - this.fail(createErrorObject(CAPTURE_INTERNAL_ERR, "Error capturing image.")); - } - } else if (requestCode == CAPTURE_VIDEO) { - // Get the uri of the video clip - Uri data = intent.getData(); - // create a file object from the uri - results.put(createMediaFile(data)); - - if (results.length() >= limit) { - // Send Uri back to JavaScript for viewing video - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results)); - } else { - // still need to capture more video clips - captureVideo(duration); - } - } - } - // If canceled - else if (resultCode == Activity.RESULT_CANCELED) { - // If we have partial results send them back to the user - if (results.length() > 0) { - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results)); - } - // user canceled the action - else { - this.fail(createErrorObject(CAPTURE_NO_MEDIA_FILES, "Canceled.")); - } - } - // If something else - else { - // If we have partial results send them back to the user - if (results.length() > 0) { - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results)); - } - // something bad happened - else { - this.fail(createErrorObject(CAPTURE_NO_MEDIA_FILES, "Did not complete!")); - } - } - } - - /** - * Creates a JSONObject that represents a File from the Uri - * - * @param data the Uri of the audio/image/video - * @return a JSONObject that represents a File - * @throws IOException - */ - private JSONObject createMediaFile(Uri data) { - DataResource dataResource = DataResource.initiateNewDataRequestForUri(data, webView.pluginManager, cordova, "Capture.createMediaFile"); - File fp = dataResource.getRealFile(); - JSONObject obj = new JSONObject(); - - try { - // 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 - // 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/")) { - obj.put("type", AUDIO_3GPP); - } else { - obj.put("type", VIDEO_3GPP); - } - } else { - obj.put("type", dataResource.getMimeType()); - } - - obj.put("lastModifiedDate", fp.lastModified()); - obj.put("size", fp.length()); - } catch (JSONException e) { - // this will never happen - e.printStackTrace(); - } - - return obj; - } - - private JSONObject createErrorObject(int code, String message) { - JSONObject obj = new JSONObject(); - try { - obj.put("code", code); - obj.put("message", message); - } catch (JSONException e) { - // This will never happen - } - return obj; - } - - /** - * Send error message to JavaScript. - * - * @param err - */ - public void fail(JSONObject err) { - this.callbackContext.error(err); - } - - - /** - * Creates a cursor that can be used to determine how many images we have. - * - * @return a cursor - */ - private Cursor queryImgDB(Uri contentStore) { - return this.cordova.getActivity().getContentResolver().query( - contentStore, - new String[] { MediaStore.Images.Media._ID }, - null, - null, - null); - } - - /** - * Used to find out if we are in a situation where the Camera Intent adds to images - * to the content store. - */ - private void checkForDuplicateImage() { - Uri contentStore = whichContentStore(); - Cursor cursor = queryImgDB(contentStore); - int currentNumOfImages = cursor.getCount(); - - // delete the duplicate file if the difference is 2 - if ((currentNumOfImages - numPics) == 2) { - cursor.moveToLast(); - int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))) - 1; - Uri uri = Uri.parse(contentStore + "/" + id); - this.cordova.getActivity().getContentResolver().delete(uri, null, null); - } - } - - /** - * Determine if we are storing the images in internal or external storage - * @return Uri - */ - private Uri whichContentStore() { - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - return android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else { - return android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI; - } - } -} diff --git a/framework/src/org/apache/cordova/CompassListener.java b/framework/src/org/apache/cordova/CompassListener.java deleted file mode 100755 index 401e0536..00000000 --- a/framework/src/org/apache/cordova/CompassListener.java +++ /dev/null @@ -1,295 +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.List; - -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaInterface; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.content.Context; - -import android.os.Handler; -import android.os.Looper; - -/** - * This class listens to the compass sensor and stores the latest heading value. - */ -public class CompassListener extends CordovaPlugin implements SensorEventListener { - - public static int STOPPED = 0; - public static int STARTING = 1; - public static int RUNNING = 2; - public static int ERROR_FAILED_TO_START = 3; - - public long TIMEOUT = 30000; // Timeout in msec to shut off listener - - int status; // status of listener - float heading; // most recent heading value - long timeStamp; // time of most recent value - long lastAccessTime; // time the value was last retrieved - int accuracy; // accuracy of the sensor - - private SensorManager sensorManager;// Sensor manager - Sensor mSensor; // Compass sensor returned by sensor manager - - private CallbackContext callbackContext; - - /** - * Constructor. - */ - public CompassListener() { - this.heading = 0; - this.timeStamp = 0; - this.setStatus(CompassListener.STOPPED); - } - - /** - * Sets the context of the Command. This can then be used to do things like - * get file paths associated with the Activity. - * - * @param cordova The context of the main Activity. - * @param webView The CordovaWebView Cordova is running in. - */ - public void initialize(CordovaInterface cordova, CordovaWebView webView) { - super.initialize(cordova, webView); - this.sensorManager = (SensorManager) cordova.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 callbackS=Context The callback id used when calling back into JavaScript. - * @return True if the action was valid. - * @throws JSONException - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - if (action.equals("start")) { - this.start(); - } - else if (action.equals("stop")) { - this.stop(); - } - else if (action.equals("getStatus")) { - int i = this.getStatus(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, i)); - } - else if (action.equals("getHeading")) { - // If not running, then this is an async call, so don't worry about waiting - if (this.status != CompassListener.RUNNING) { - int r = this.start(); - if (r == CompassListener.ERROR_FAILED_TO_START) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START)); - return true; - } - // Set a timeout callback on the main thread. - Handler handler = new Handler(Looper.getMainLooper()); - handler.postDelayed(new Runnable() { - public void run() { - CompassListener.this.timeout(); - } - }, 2000); - } - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getCompassHeading())); - } - else if (action.equals("setTimeout")) { - this.setTimeout(args.getLong(0)); - } - else if (action.equals("getTimeout")) { - long l = this.getTimeout(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l)); - } else { - // Unsupported action - return false; - } - return true; - } - - /** - * Called when listener is to be shut down and object is being destroyed. - */ - public void onDestroy() { - this.stop(); - } - - /** - * Called when app has navigated and JS listeners have been destroyed. - */ - public void onReset() { - this.stop(); - } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - /** - * Start listening for compass sensor. - * - * @return status of listener - */ - public int start() { - - // If already starting or running, then just return - if ((this.status == CompassListener.RUNNING) || (this.status == CompassListener.STARTING)) { - return this.status; - } - - // Get compass sensor from sensor manager - @SuppressWarnings("deprecation") - List list = this.sensorManager.getSensorList(Sensor.TYPE_ORIENTATION); - - // 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_NORMAL); - this.lastAccessTime = System.currentTimeMillis(); - this.setStatus(CompassListener.STARTING); - } - - // If error, then set status to error - else { - this.setStatus(CompassListener.ERROR_FAILED_TO_START); - } - - return this.status; - } - - /** - * Stop listening to compass sensor. - */ - public void stop() { - if (this.status != CompassListener.STOPPED) { - this.sensorManager.unregisterListener(this); - } - this.setStatus(CompassListener.STOPPED); - } - - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // TODO Auto-generated method stub - } - - /** - * Called after a delay to time out if the listener has not attached fast enough. - */ - private void timeout() { - if (this.status == CompassListener.STARTING) { - this.setStatus(CompassListener.ERROR_FAILED_TO_START); - if (this.callbackContext != null) { - this.callbackContext.error("Compass listener failed to start."); - } - } - } - - /** - * Sensor listener event. - * - * @param SensorEvent event - */ - public void onSensorChanged(SensorEvent event) { - - // We only care about the orientation as far as it refers to Magnetic North - float heading = event.values[0]; - - // Save heading - this.timeStamp = System.currentTimeMillis(); - this.heading = heading; - this.setStatus(CompassListener.RUNNING); - - // If heading hasn't been read for TIMEOUT time, then turn off compass sensor to save power - if ((this.timeStamp - this.lastAccessTime) > this.TIMEOUT) { - this.stop(); - } - } - - /** - * Get status of compass sensor. - * - * @return status - */ - public int getStatus() { - return this.status; - } - - /** - * Get the most recent compass heading. - * - * @return heading - */ - public float getHeading() { - this.lastAccessTime = System.currentTimeMillis(); - return this.heading; - } - - /** - * Set the timeout to turn off compass sensor if getHeading() hasn't been called. - * - * @param timeout Timeout in msec. - */ - public void setTimeout(long timeout) { - this.TIMEOUT = timeout; - } - - /** - * Get the timeout to turn off compass sensor if getHeading() hasn't been called. - * - * @return timeout in msec - */ - public long getTimeout() { - return this.TIMEOUT; - } - - /** - * Set the status and send it to JavaScript. - * @param status - */ - private void setStatus(int status) { - this.status = status; - } - - /** - * Create the CompassHeading JSON object to be returned to JavaScript - * - * @return a compass heading - */ - private JSONObject getCompassHeading() throws JSONException { - JSONObject obj = new JSONObject(); - - obj.put("magneticHeading", this.getHeading()); - obj.put("trueHeading", this.getHeading()); - // Since the magnetic and true heading are always the same our and accuracy - // is defined as the difference between true and magnetic always return zero - obj.put("headingAccuracy", 0); - obj.put("timestamp", this.timeStamp); - - return obj; - } - -} diff --git a/framework/src/org/apache/cordova/ContactAccessor.java b/framework/src/org/apache/cordova/ContactAccessor.java deleted file mode 100644 index 04b43420..00000000 --- a/framework/src/org/apache/cordova/ContactAccessor.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed 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.HashMap; - -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; - -/** - * This abstract class defines SDK-independent API for communication with - * Contacts Provider. The actual implementation used by the application depends - * on the level of API available on the device. If the API level is Cupcake or - * Donut, we want to use the {@link ContactAccessorSdk3_4} class. If it is - * Eclair or higher, we want to use {@link ContactAccessorSdk5}. - */ -public abstract class ContactAccessor { - - protected final String LOG_TAG = "ContactsAccessor"; - protected CordovaInterface mApp; - protected WebView mView; - - /** - * Check to see if the data associated with the key is required to - * be populated in the Contact object. - * @param key - * @param map created by running buildPopulationSet. - * @return true if the key data is required - */ - protected boolean isRequired(String key, HashMap map) { - Boolean retVal = map.get(key); - return (retVal == null) ? false : retVal.booleanValue(); - } - - /** - * Create a hash map of what data needs to be populated in the Contact object - * @param fields the list of fields to populate - * @return the hash map of required data - */ - protected HashMap buildPopulationSet(JSONArray fields) { - HashMap map = new HashMap(); - - String key; - try { - if (fields.length() == 1 && fields.getString(0).equals("*")) { - map.put("displayName", true); - map.put("name", true); - map.put("nickname", true); - map.put("phoneNumbers", true); - map.put("emails", true); - map.put("addresses", true); - map.put("ims", true); - map.put("organizations", true); - map.put("birthday", true); - map.put("note", true); - map.put("urls", true); - map.put("photos", true); - map.put("categories", true); - } - else { - for (int i=0; i - * This implementation has several advantages: - *
    - *
  • It sees contacts from multiple accounts. - *
  • It works with aggregated contacts. So for example, if the contact is the result - * of aggregation of two raw contacts from different accounts, it may return the name from - * one and the phone number from the other. - *
  • It is efficient because it uses the more efficient current API. - *
  • Not obvious in this particular example, but it has access to new kinds - * of data available exclusively through the new APIs. Exercise for the reader: add support - * for nickname (see {@link android.provider.ContactsContract.CommonDataKinds.Nickname}) or - * social status updates (see {@link android.provider.ContactsContract.StatusUpdates}). - *
- */ - -public class ContactAccessorSdk5 extends ContactAccessor { - - /** - * Keep the photo size under the 1 MB blog limit. - */ - private static final long MAX_PHOTO_SIZE = 1048576; - - private static final String EMAIL_REGEXP = ".+@.+\\.+.+"; /* @.*/ - - /** - * 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.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, 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; - - 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 = "%"; - } - - - //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); - - // 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.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 - Set contactIds = new HashSet(); - int idColumn = -1; - while (idCursor.moveToNext()) { - if (idColumn < 0) { - idColumn = idCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID); - } - contactIds.add(idCursor.getString(idColumn)); - } - idCursor.close(); - - // Build a query that only looks at ids - WhereOptions idOptions = buildIdClause(contactIds, searchTerm); - - // Determine which columns we should be fetching. - HashSet columnsToFetch = new HashSet(); - columnsToFetch.add(ContactsContract.Data.CONTACT_ID); - columnsToFetch.add(ContactsContract.Data.RAW_CONTACT_ID); - columnsToFetch.add(ContactsContract.Data.MIMETYPE); - - if (isRequired("displayName", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); - } - if (isRequired("name", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.PREFIX); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.SUFFIX); - } - if (isRequired("phoneNumbers", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Phone._ID); - columnsToFetch.add(ContactsContract.CommonDataKinds.Phone.NUMBER); - columnsToFetch.add(ContactsContract.CommonDataKinds.Phone.TYPE); - } - if (isRequired("emails", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Email._ID); - columnsToFetch.add(ContactsContract.CommonDataKinds.Email.DATA); - columnsToFetch.add(ContactsContract.CommonDataKinds.Email.TYPE); - } - if (isRequired("addresses", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal._ID); - columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TYPE); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.STREET); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.CITY); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.REGION); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE); - columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY); - } - if (isRequired("organizations", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Organization._ID); - columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TYPE); - columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.DEPARTMENT); - columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.COMPANY); - columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TITLE); - } - if (isRequired("ims", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Im._ID); - columnsToFetch.add(ContactsContract.CommonDataKinds.Im.DATA); - columnsToFetch.add(ContactsContract.CommonDataKinds.Im.TYPE); - } - if (isRequired("note", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Note.NOTE); - } - if (isRequired("nickname", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Nickname.NAME); - } - if (isRequired("urls", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Website._ID); - columnsToFetch.add(ContactsContract.CommonDataKinds.Website.URL); - columnsToFetch.add(ContactsContract.CommonDataKinds.Website.TYPE); - } - if (isRequired("birthday", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Event.START_DATE); - columnsToFetch.add(ContactsContract.CommonDataKinds.Event.TYPE); - } - if (isRequired("photos", populate)) { - columnsToFetch.add(ContactsContract.CommonDataKinds.Photo._ID); - } - - // Do the id query - Cursor c = mApp.getActivity().getContentResolver().query(ContactsContract.Data.CONTENT_URI, - columnsToFetch.toArray(new String[] {}), - 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 }, - ContactsContract.Data.CONTACT_ID + " ASC"); - - JSONArray fields = new JSONArray(); - fields.put("*"); - - HashMap populate = buildPopulationSet(fields); - - JSONArray contacts = populateContactArray(1, populate, c); - - if (contacts.length() == 1) { - return contacts.getJSONObject(0); - } 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 - */ - private JSONArray populateContactArray(int limit, - HashMap populate, Cursor c) { - - 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(); - - // Column indices - int colContactId = c.getColumnIndex(ContactsContract.Data.CONTACT_ID); - int colRawContactId = c.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID); - int colMimetype = c.getColumnIndex(ContactsContract.Data.MIMETYPE); - int colDisplayName = c.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); - int colNote = c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE); - int colNickname = c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME); - int colBirthday = c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE); - int colEventType = c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE); - - if (c.getCount() > 0) { - while (c.moveToNext() && (contacts.length() <= (limit - 1))) { - try { - contactId = c.getString(colContactId); - rawId = c.getString(colRawContactId); - - // 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)); - - // 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; - } - - // 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(colMimetype); - - if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) { - contact.put("displayName", c.getString(colDisplayName)); - } - - if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) - && 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(colNote)); - } - else if (mimetype.equals(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) - && isRequired("nickname", populate)) { - contact.put("nickname", c.getString(colNickname)); - } - 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 (isRequired("birthday", populate) && - ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(colEventType)) { - contact.put("birthday", c.getString(colBirthday)); - } - } - 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(); - 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(); - - // 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; - } - - /** - * 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); - } - if (addresses.length() > 0) { - contact.put("addresses", addresses); - } - if (phones.length() > 0) { - contact.put("phoneNumbers", phones); - } - if (emails.length() > 0) { - contact.put("emails", emails); - } - if (ims.length() > 0) { - contact.put("ims", ims); - } - if (websites.length() > 0) { - contact.put("urls", websites); - } - if (photos.length() > 0) { - contact.put("photos", photos); - } - } 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 - * @param searchTerm the string to search for - * @return an object containing the selection and selection args - */ - private WhereOptions buildWhereClause(JSONArray fields, String searchTerm) { - - ArrayList where = new ArrayList(); - ArrayList whereArgs = new ArrayList(); - - WhereOptions options = new WhereOptions(); - - /* - * Special case where the user wants all fields returned - */ - if (isWildCardSearch(fields)) { - // Get all contacts with all properties - if ("%".equals(searchTerm)) { - options.setWhere("(" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? )"); - options.setWhereArgs(new String[] { searchTerm }); - return options; - } else { - // 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 " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); - where.add("(" + dbMap.get("nickname") + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE); - where.add("(" + dbMap.get("phoneNumbers") + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); - where.add("(" + dbMap.get("emails") + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE); - where.add("(" + dbMap.get("addresses") + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE); - where.add("(" + dbMap.get("ims") + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE); - where.add("(" + dbMap.get("organizations") + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); - where.add("(" + dbMap.get("note") + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE); - where.add("(" + dbMap.get("urls") + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE); - } - } - - /* - * 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 < fields.length(); i++) { - key = fields.getString(i); - - if (key.equals("id")) { - where.add("(" + dbMap.get(key) + " = ? )"); - whereArgs.add(searchTerm.substring(1, searchTerm.length() - 1)); - } - else if (key.startsWith("displayName")) { - where.add("(" + dbMap.get(key) + " LIKE ? )"); - whereArgs.add(searchTerm); - } - else if (key.startsWith("name")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); - } - else if (key.startsWith("nickname")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE); - } - else if (key.startsWith("phoneNumbers")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); - } - else if (key.startsWith("emails")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE); - } - else if (key.startsWith("addresses")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE); - } - else if (key.startsWith("ims")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE); - } - else if (key.startsWith("organizations")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); - } - // else if (key.startsWith("birthday")) { -// where.add("(" + dbMap.get(key) + " LIKE ? AND " -// + ContactsContract.Data.MIMETYPE + " = ? )"); -// } - else if (key.startsWith("note")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE); - } - else if (key.startsWith("urls")) { - where.add("(" + dbMap.get(key) + " LIKE ? AND " - + ContactsContract.Data.MIMETYPE + " = ? )"); - whereArgs.add(searchTerm); - whereArgs.add(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE); - } - } - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - - // Creating the where string - StringBuffer selection = new StringBuffer(); - for (int i = 0; i < where.size(); i++) { - selection.append(where.get(i)); - if (i != (where.size() - 1)) { - selection.append(" OR "); - } - } - options.setWhere(selection.toString()); - - // Creating the where args array - String[] selectionArgs = new String[whereArgs.size()]; - for (int i = 0; i < whereArgs.size(); i++) { - selectionArgs[i] = whereArgs.get(i); - } - options.setWhereArgs(selectionArgs); - - return options; - } - - /** - * If the user passes in the '*' wildcard character for search then they want all fields for each contact - * - * @param fields - * @return true if wildcard search requested, false otherwise - */ - private boolean isWildCardSearch(JSONArray fields) { - // Only do a wildcard search if we are passed ["*"] - if (fields.length() == 1) { - try { - if ("*".equals(fields.getString(0))) { - return true; - } - } catch (JSONException e) { - return false; - } - } - return false; - } - - /** - * Create a ContactOrganization JSONObject - * @param cursor the current database row - * @return a JSONObject representing a ContactOrganization - */ - private JSONObject organizationQuery(Cursor cursor) { - JSONObject organization = new JSONObject(); - try { - organization.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization._ID))); - organization.put("pref", false); // Android does not store pref attribute - organization.put("type", getOrgType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.TYPE)))); - 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; - } - - /** - * Create a ContactAddress JSONObject - * @param cursor the current database row - * @return a JSONObject representing a ContactAddress - */ - 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; - } - - /** - * 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)); - - // 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); - } - - 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", getImType(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.PROTOCOL)))); - } 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) { - // Delete all the phones - if (phones.length() == 0) { - ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) - .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + - ContactsContract.Data.MIMETYPE + "=?", - new String[] { "" + rawId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE }) - .build()); - } - // Modify or add a phone - else { - 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) { - // Delete all the emails - if (emails.length() == 0) { - ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) - .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + - ContactsContract.Data.MIMETYPE + "=?", - new String[] { "" + rawId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE }) - .build()); - } - // Modify or add a email - else { - 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) { - // Delete all the addresses - if (addresses.length() == 0) { - ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) - .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + - ContactsContract.Data.MIMETYPE + "=?", - new String[] { "" + rawId, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE }) - .build()); - } - // Modify or add a address - else { - 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) { - // Delete all the organizations - if (organizations.length() == 0) { - ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) - .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + - ContactsContract.Data.MIMETYPE + "=?", - new String[] { "" + rawId, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE }) - .build()); - } - // Modify or add a organization - else { - 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) { - // Delete all the ims - if (ims.length() == 0) { - ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) - .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + - ContactsContract.Data.MIMETYPE + "=?", - new String[] { "" + rawId, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE }) - .build()); - } - // Modify or add a im - else { - 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, getImType(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("urls"); - if (websites != null) { - // Delete all the websites - if (websites.length() == 0) { - Log.d(LOG_TAG, "This means we should be deleting all the phone numbers."); - ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) - .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + - ContactsContract.Data.MIMETYPE + "=?", - new String[] { "" + rawId, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE }) - .build()); - } - // Modify or add a website - else { - 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) { - // Delete all the photos - if (photos.length() == 0) { - ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI) - .withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + - ContactsContract.Data.MIMETYPE + "=?", - new String[] { "" + rawId, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE }) - .build()); - } - // Modify or add a photo - else { - 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 success 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.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, getImType(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, getContactType(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 - */ - private InputStream getPathFromUri(String path) throws IOException { - if (path.startsWith("content:")) { - Uri uri = Uri.parse(path); - return mApp.getActivity().getContentResolver().openInputStream(uri); - } - if (path.startsWith("http:") || path.startsWith("https:") || path.startsWith("file:")) { - URL url = new URL(path); - return url.openStream(); - } - else { - return new FileInputStream(path); - } - } - - /** - * 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(); - - //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"); - } - - //Add 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); - insertPhone(ops, phone); - } - } - } catch (JSONException e) { - Log.d(LOG_TAG, "Could not get phone numbers"); - } - - // Add 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); - insertEmail(ops, email); - } - } - } catch (JSONException e) { - Log.d(LOG_TAG, "Could not get emails"); - } - - // Add 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); - insertAddress(ops, address); - } - } - } catch (JSONException e) { - Log.d(LOG_TAG, "Could not get addresses"); - } - - // Add 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); - insertOrganization(ops, org); - } - } - } catch (JSONException e) { - Log.d(LOG_TAG, "Could not get organizations"); - } - - // Add 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); - insertIm(ops, im); - } - } - } 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("urls"); - if (websites != null) { - for (int i = 0; i < websites.length(); i++) { - JSONObject website = (JSONObject) websites.get(i); - insertWebsite(ops, website); - } - } - } catch (JSONException e) { - Log.d(LOG_TAG, "Could not get websites"); - } - - // Add birthday - String birthday = getJsonString(contact, "birthday"); - if (birthday != null) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) - .withValue(ContactsContract.CommonDataKinds.Event.START_DATE, birthday) - .build()); - } - - // Add 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); - insertPhoto(ops, photo); - } - } - } catch (JSONException e) { - Log.d(LOG_TAG, "Could not get photos"); - } - - String newId = null; - //Add contact - try { - ContentProviderResult[] cpResults = mApp.getActivity().getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); - if (cpResults.length >= 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); - } - 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) { - int result = 0; - 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.getActivity().getContentResolver().delete(uri, null, null); - } else { - 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 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; - } - - /** - * Converts a string from the W3C Contact API to it's Android int value. - * @param string - * @return Android int value - */ - private int getContactType(String string) { - int type = ContactsContract.CommonDataKinds.Email.TYPE_OTHER; - if (string != null) { - if ("home".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Email.TYPE_HOME; - } - else if ("work".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Email.TYPE_WORK; - } - else if ("other".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Email.TYPE_OTHER; - } - else if ("mobile".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Email.TYPE_MOBILE; - } - else if ("custom".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Email.TYPE_CUSTOM; - } - } - return type; - } - - /** - * getPhoneType converts an Android phone type into a string - * @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; - } - return stringType; - } - - /** - * Converts a string from the W3C Contact API to it's Android int value. - * @param string - * @return Android int value - */ - private int getOrgType(String string) { - int type = ContactsContract.CommonDataKinds.Organization.TYPE_OTHER; - if (string != null) { - if ("work".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Organization.TYPE_WORK; - } - else if ("other".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Organization.TYPE_OTHER; - } - else if ("custom".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Organization.TYPE_CUSTOM; - } - } - return type; - } - - /** - * getPhoneType converts an Android phone type into a string - * @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; - } - return stringType; - } - - /** - * Converts a string from the W3C Contact API to it's Android int value. - * @param string - * @return Android int value - */ - private int getAddressType(String string) { - int type = ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER; - if (string != null) { - if ("work".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK; - } - else if ("other".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER; - } - else if ("home".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME; - } - } - return type; - } - - /** - * getPhoneType converts an Android phone type into a string - * @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; - } - return stringType; - } - - /** - * Converts a string from the W3C Contact API to it's Android int value. - * @param string - * @return Android int value - */ - private int getImType(String string) { - int type = ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM; - if (string != null) { - if ("aim".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM; - } - else if ("google talk".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK; - } - else if ("icq".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ; - } - else if ("jabber".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER; - } - else if ("msn".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN; - } - else if ("netmeeting".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING; - } - else if ("qq".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ; - } - else if ("skype".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE; - } - else if ("yahoo".equals(string.toLowerCase())) { - return ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO; - } - } - return type; - } - - /** - * getPhoneType converts an Android phone type into a string - * @param type - * @return phone type as string. - */ - private String getImType(int type) { - String stringType; - switch (type) { - case ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM: - stringType = "AIM"; - break; - case ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK: - stringType = "Google Talk"; - break; - case ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ: - stringType = "ICQ"; - break; - case ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER: - stringType = "Jabber"; - break; - case ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN: - stringType = "MSN"; - break; - case ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING: - stringType = "NetMeeting"; - break; - case ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ: - stringType = "QQ"; - break; - case ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE: - stringType = "Skype"; - break; - case ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO: - stringType = "Yahoo"; - break; - default: - stringType = "custom"; - break; - } - return stringType; - } -} - diff --git a/framework/src/org/apache/cordova/ContactManager.java b/framework/src/org/apache/cordova/ContactManager.java deleted file mode 100755 index 8ea64a64..00000000 --- a/framework/src/org/apache/cordova/ContactManager.java +++ /dev/null @@ -1,122 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import android.util.Log; - -public class ContactManager extends CordovaPlugin { - - private ContactAccessor contactAccessor; - private static final String LOG_TAG = "Contact Query"; - - public static final int UNKNOWN_ERROR = 0; - public static final int INVALID_ARGUMENT_ERROR = 1; - public static final int TIMEOUT_ERROR = 2; - public static final int PENDING_OPERATION_ERROR = 3; - public static final int IO_ERROR = 4; - public static final int NOT_SUPPORTED_ERROR = 5; - public static final int PERMISSION_DENIED_ERROR = 20; - - /** - * Constructor. - */ - public ContactManager() { - } - - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArray of arguments for the plugin. - * @param callbackContext The callback context used when calling back into JavaScript. - * @return True if the action was valid, false otherwise. - */ - public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { - /** - * Check to see if we are on an Android 1.X device. If we are return an error as we - * do not support this as of Cordova 1.0. - */ - if (android.os.Build.VERSION.RELEASE.startsWith("1.")) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ContactManager.NOT_SUPPORTED_ERROR)); - return true; - } - - /** - * Only create the contactAccessor after we check the Android version or the program will crash - * older phones. - */ - if (this.contactAccessor == null) { - this.contactAccessor = new ContactAccessorSdk5(this.webView, this.cordova); - } - - if (action.equals("search")) { - final JSONArray filter = args.getJSONArray(0); - final JSONObject options = args.getJSONObject(1); - this.cordova.getThreadPool().execute(new Runnable() { - public void run() { - JSONArray res = contactAccessor.search(filter, options); - callbackContext.success(res); - } - }); - } - else if (action.equals("save")) { - final JSONObject contact = args.getJSONObject(0); - this.cordova.getThreadPool().execute(new Runnable() { - public void run() { - JSONObject res = null; - String id = contactAccessor.save(contact); - if (id != null) { - try { - res = contactAccessor.getContactById(id); - } catch (JSONException e) { - Log.e(LOG_TAG, "JSON fail.", e); - } - } - if (res != null) { - callbackContext.success(res); - } else { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR)); - } - } - }); - } - else if (action.equals("remove")) { - final String contactId = args.getString(0); - this.cordova.getThreadPool().execute(new Runnable() { - public void run() { - if (contactAccessor.remove(contactId)) { - callbackContext.success(); - } else { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR)); - } - } - }); - } - else { - return false; - } - return true; - } -} diff --git a/framework/src/org/apache/cordova/Echo.java b/framework/src/org/apache/cordova/Echo.java deleted file mode 100644 index aaebe024..00000000 --- a/framework/src/org/apache/cordova/Echo.java +++ /dev/null @@ -1,48 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.json.JSONException; - -public class Echo extends CordovaPlugin { - - @Override - public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { - if ("echo".equals(action)) { - final String result = args.isNull(0) ? null : args.getString(0); - callbackContext.success(result); - return true; - } else if ("echoAsync".equals(action)) { - final String result = args.isNull(0) ? null : args.getString(0); - cordova.getThreadPool().execute(new Runnable() { - public void run() { - callbackContext.success(result); - } - }); - return true; - } else if ("echoArrayBuffer".equals(action)) { - final byte[] result = args.getArrayBuffer(0); - callbackContext.success(result); - return true; - } - return false; - } -} diff --git a/framework/src/org/apache/cordova/FileProgressResult.java b/framework/src/org/apache/cordova/FileProgressResult.java deleted file mode 100644 index d9811755..00000000 --- a/framework/src/org/apache/cordova/FileProgressResult.java +++ /dev/null @@ -1,63 +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 org.json.JSONException; -import org.json.JSONObject; - -/** - * Encapsulates in-progress status of uploading or downloading a file to a remote server. - */ -public class FileProgressResult { - - private boolean lengthComputable = false; // declares whether total is known - private long loaded = 0; // bytes sent so far - private long total = 0; // bytes total, if known - - public boolean getLengthComputable() { - return lengthComputable; - } - - public void setLengthComputable(boolean computable) { - this.lengthComputable = computable; - } - - public long getLoaded() { - return loaded; - } - - public void setLoaded(long bytes) { - this.loaded = bytes; - } - - public long getTotal() { - return total; - } - - public void setTotal(long bytes) { - this.total = bytes; - } - - public JSONObject toJSONObject() throws JSONException { - return new JSONObject( - "{loaded:" + loaded + - ",total:" + total + - ",lengthComputable:" + (lengthComputable ? "true" : "false") + "}"); - } -} diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java deleted file mode 100644 index c87683f6..00000000 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ /dev/null @@ -1,964 +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.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLDecoder; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.HashMap; -import java.util.Iterator; -import java.util.zip.GZIPInputStream; -import java.util.zip.Inflater; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.net.Uri; -import android.os.Build; -import android.util.Log; -import android.webkit.CookieManager; - -import com.squareup.okhttp.OkHttpClient; - -public class FileTransfer extends CordovaPlugin { - - private static final String LOG_TAG = "FileTransfer"; - private static final String LINE_START = "--"; - private static final String LINE_END = "\r\n"; - private static final String BOUNDARY = "+++++"; - - public static int FILE_NOT_FOUND_ERR = 1; - public static int INVALID_URL_ERR = 2; - public static int CONNECTION_ERR = 3; - public static int ABORTED_ERR = 4; - - private static HashMap activeRequests = new HashMap(); - private static final int MAX_BUFFER_SIZE = 16 * 1024; - - private static OkHttpClient httpClient = new OkHttpClient(); - - private static final class RequestContext { - String source; - String target; - File targetFile; - CallbackContext callbackContext; - InputStream currentInputStream; - OutputStream currentOutputStream; - boolean aborted; - RequestContext(String source, String target, CallbackContext callbackContext) { - this.source = source; - this.target = target; - this.callbackContext = callbackContext; - } - void sendPluginResult(PluginResult pluginResult) { - synchronized (this) { - if (!aborted) { - callbackContext.sendPluginResult(pluginResult); - } - } - } - } - - /** - * Adds an interface method to an InputStream to return the number of bytes - * read from the raw stream. This is used to track total progress against - * the HTTP Content-Length header value from the server. - */ - private static abstract class TrackingInputStream extends FilterInputStream { - public TrackingInputStream(final InputStream in) { - super(in); - } - public abstract long getTotalRawBytesRead(); - } - - private static class ExposedGZIPInputStream extends GZIPInputStream { - public ExposedGZIPInputStream(final InputStream in) throws IOException { - super(in); - } - public Inflater getInflater() { - return inf; - } - } - - /** - * Provides raw bytes-read tracking for a GZIP input stream. Reports the - * total number of compressed bytes read from the input, rather than the - * number of uncompressed bytes. - */ - private static class TrackingGZIPInputStream extends TrackingInputStream { - private ExposedGZIPInputStream gzin; - public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException { - super(gzin); - this.gzin = gzin; - } - public long getTotalRawBytesRead() { - return gzin.getInflater().getBytesRead(); - } - } - - /** - * Provides simple total-bytes-read tracking for an existing InputStream - */ - private static class TrackingHTTPInputStream extends TrackingInputStream { - private long bytesRead = 0; - public TrackingHTTPInputStream(InputStream stream) { - super(stream); - } - - private int updateBytesRead(int newBytesRead) { - if (newBytesRead != -1) { - bytesRead += newBytesRead; - } - return newBytesRead; - } - - @Override - public int read() throws IOException { - return updateBytesRead(super.read()); - } - - @Override - public int read(byte[] buffer) throws IOException { - return updateBytesRead(super.read(buffer)); - } - - @Override - public int read(byte[] bytes, int offset, int count) throws IOException { - return updateBytesRead(super.read(bytes, offset, count)); - } - - public long getTotalRawBytesRead() { - return bytesRead; - } - } - - /** - * Works around a bug on Android 2.3. - * http://code.google.com/p/android/issues/detail?id=14562 - */ - private static final class DoneHandlerInputStream extends TrackingHTTPInputStream { - private boolean done; - - public DoneHandlerInputStream(InputStream stream) { - super(stream); - } - - @Override - public int read() throws IOException { - int result = done ? -1 : super.read(); - done = (result == -1); - return result; - } - - @Override - public int read(byte[] buffer) throws IOException { - int result = done ? -1 : super.read(buffer); - done = (result == -1); - return result; - } - - @Override - public int read(byte[] bytes, int offset, int count) throws IOException { - int result = done ? -1 : super.read(bytes, offset, count); - done = (result == -1); - return result; - } - } - - @Override - public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { - if (action.equals("upload") || action.equals("download")) { - String source = args.getString(0); - String target = args.getString(1); - - if (action.equals("upload")) { - try { - source = URLDecoder.decode(source, "UTF-8"); - upload(source, target, args, callbackContext); - } catch (UnsupportedEncodingException e) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.MALFORMED_URL_EXCEPTION, "UTF-8 error.")); - } - } else { - download(source, target, args, callbackContext); - } - return true; - } else if (action.equals("abort")) { - String objectId = args.getString(0); - abort(objectId); - callbackContext.success(); - return true; - } - return false; - } - - private static void addHeadersToRequest(URLConnection connection, JSONObject headers) { - try { - for (Iterator iter = headers.keys(); iter.hasNext(); ) { - String headerKey = iter.next().toString(); - JSONArray headerValues = headers.optJSONArray(headerKey); - if (headerValues == null) { - headerValues = new JSONArray(); - headerValues.put(headers.getString(headerKey)); - } - connection.setRequestProperty(headerKey, headerValues.getString(0)); - for (int i = 1; i < headerValues.length(); ++i) { - connection.addRequestProperty(headerKey, headerValues.getString(i)); - } - } - } catch (JSONException e1) { - // No headers to be manipulated! - } - } - - /** - * Uploads the specified file to the server URL provided using an HTTP multipart request. - * @param source Full path of the file on the file system - * @param target URL of the server to receive the file - * @param args JSON Array of args - * @param callbackContext callback id for optional progress reports - * - * args[2] fileKey Name of file request parameter - * args[3] fileName File name to be used on server - * args[4] mimeType Describes file content type - * args[5] params key:value pairs of user-defined parameters - * @return FileUploadResult containing result of upload request - */ - private void upload(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException { - Log.d(LOG_TAG, "upload " + source + " to " + target); - - // Setup the options - final String fileKey = getArgument(args, 2, "file"); - final String fileName = getArgument(args, 3, "image.jpg"); - final String mimeType = getArgument(args, 4, "image/jpeg"); - final JSONObject params = args.optJSONObject(5) == null ? new JSONObject() : args.optJSONObject(5); - final boolean trustEveryone = args.optBoolean(6); - // Always use chunked mode unless set to false as per API - final boolean chunkedMode = args.optBoolean(7) || args.isNull(7); - // Look for headers on the params map for backwards compatibility with older Cordova versions. - final JSONObject headers = args.optJSONObject(8) == null ? params.optJSONObject("headers") : args.optJSONObject(8); - final String objectId = args.getString(9); - final String httpMethod = getArgument(args, 10, "POST"); - - Log.d(LOG_TAG, "fileKey: " + fileKey); - Log.d(LOG_TAG, "fileName: " + fileName); - Log.d(LOG_TAG, "mimeType: " + mimeType); - Log.d(LOG_TAG, "params: " + params); - Log.d(LOG_TAG, "trustEveryone: " + trustEveryone); - Log.d(LOG_TAG, "chunkedMode: " + chunkedMode); - Log.d(LOG_TAG, "headers: " + headers); - Log.d(LOG_TAG, "objectId: " + objectId); - Log.d(LOG_TAG, "httpMethod: " + httpMethod); - - final URL url; - try { - url = new URL(target); - } catch (MalformedURLException e) { - JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0); - Log.e(LOG_TAG, error.toString(), e); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - return; - } - final boolean useHttps = url.getProtocol().equals("https"); - - final RequestContext context = new RequestContext(source, target, callbackContext); - synchronized (activeRequests) { - activeRequests.put(objectId, context); - } - - cordova.getThreadPool().execute(new Runnable() { - public void run() { - if (context.aborted) { - return; - } - HttpURLConnection conn = null; - HostnameVerifier oldHostnameVerifier = null; - SSLSocketFactory oldSocketFactory = null; - int totalBytes = 0; - int fixedLength = -1; - try { - // Create return object - FileUploadResult result = new FileUploadResult(); - FileProgressResult progress = new FileProgressResult(); - - //------------------ CLIENT REQUEST - // Open a HTTP connection to the URL based on protocol - if (useHttps) { - // Using standard HTTPS connection. Will not allow self signed certificate - if (!trustEveryone) { - conn = (HttpsURLConnection) httpClient.open(url); - } - // Use our HTTPS connection that blindly trusts everyone. - // This should only be used in debug environments - else { - // Setup the HTTPS connection class to trust everyone - HttpsURLConnection https = (HttpsURLConnection) httpClient.open(url); - oldSocketFactory = trustAllHosts(https); - // Save the current hostnameVerifier - oldHostnameVerifier = https.getHostnameVerifier(); - // Setup the connection not to verify hostnames - https.setHostnameVerifier(DO_NOT_VERIFY); - conn = https; - } - } - // Return a standard HTTP connection - else { - conn = httpClient.open(url); - } - - // Allow Inputs - conn.setDoInput(true); - - // Allow Outputs - conn.setDoOutput(true); - - // Don't use a cached copy. - conn.setUseCaches(false); - - // Use a post method. - conn.setRequestMethod(httpMethod); - conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY); - - // Set the cookies on the response - String cookie = CookieManager.getInstance().getCookie(target); - if (cookie != null) { - conn.setRequestProperty("Cookie", cookie); - } - - // Handle the other headers - if (headers != null) { - addHeadersToRequest(conn, headers); - } - - /* - * Store the non-file portions of the multipart data as a string, so that we can add it - * to the contentSize, since it is part of the body of the HTTP request. - */ - StringBuilder beforeData = new StringBuilder(); - try { - for (Iterator iter = params.keys(); iter.hasNext();) { - Object key = iter.next(); - if(!String.valueOf(key).equals("headers")) - { - beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END); - beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append('"'); - beforeData.append(LINE_END).append(LINE_END); - beforeData.append(params.getString(key.toString())); - beforeData.append(LINE_END); - } - } - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - - beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END); - beforeData.append("Content-Disposition: form-data; name=\"").append(fileKey).append("\";"); - beforeData.append(" filename=\"").append(fileName).append('"').append(LINE_END); - beforeData.append("Content-Type: ").append(mimeType).append(LINE_END).append(LINE_END); - byte[] beforeDataBytes = beforeData.toString().getBytes("UTF-8"); - byte[] tailParamsBytes = (LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END).getBytes("UTF-8"); - - - // Get a input stream of the file on the phone - InputStream sourceInputStream = getPathFromUri(source); - - int stringLength = beforeDataBytes.length + tailParamsBytes.length; - if (sourceInputStream instanceof FileInputStream) { - fixedLength = (int) ((FileInputStream)sourceInputStream).getChannel().size() + stringLength; - progress.setLengthComputable(true); - progress.setTotal(fixedLength); - } - Log.d(LOG_TAG, "Content Length: " + fixedLength); - // setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices. - // http://code.google.com/p/android/issues/detail?id=3164 - // It also causes OOM if HTTPS is used, even on newer devices. - boolean useChunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps); - useChunkedMode = useChunkedMode || (fixedLength == -1); - - if (useChunkedMode) { - conn.setChunkedStreamingMode(MAX_BUFFER_SIZE); - // Although setChunkedStreamingMode sets this header, setting it explicitly here works - // around an OutOfMemoryException when using https. - conn.setRequestProperty("Transfer-Encoding", "chunked"); - } else { - conn.setFixedLengthStreamingMode(fixedLength); - } - - conn.connect(); - - OutputStream sendStream = null; - try { - sendStream = conn.getOutputStream(); - synchronized (context) { - if (context.aborted) { - return; - } - context.currentOutputStream = sendStream; - } - //We don't want to change encoding, we just want this to write for all Unicode. - sendStream.write(beforeDataBytes); - totalBytes += beforeDataBytes.length; - - // create a buffer of maximum size - int bytesAvailable = sourceInputStream.available(); - int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE); - byte[] buffer = new byte[bufferSize]; - - // read file and write it into form... - int bytesRead = sourceInputStream.read(buffer, 0, bufferSize); - - long prevBytesRead = 0; - while (bytesRead > 0) { - result.setBytesSent(totalBytes); - sendStream.write(buffer, 0, bytesRead); - totalBytes += bytesRead; - if (totalBytes > prevBytesRead + 102400) { - prevBytesRead = totalBytes; - Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes"); - } - bytesAvailable = sourceInputStream.available(); - bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE); - bytesRead = sourceInputStream.read(buffer, 0, bufferSize); - - // Send a progress event. - progress.setLoaded(totalBytes); - PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); - progressResult.setKeepCallback(true); - context.sendPluginResult(progressResult); - } - - // send multipart form data necessary after file data... - sendStream.write(tailParamsBytes); - totalBytes += tailParamsBytes.length; - sendStream.flush(); - } finally { - safeClose(sourceInputStream); - safeClose(sendStream); - } - context.currentOutputStream = null; - Log.d(LOG_TAG, "Sent " + totalBytes + " of " + fixedLength); - - //------------------ read the SERVER RESPONSE - String responseString; - int responseCode = conn.getResponseCode(); - Log.d(LOG_TAG, "response code: " + responseCode); - Log.d(LOG_TAG, "response headers: " + conn.getHeaderFields()); - TrackingInputStream inStream = null; - try { - inStream = getInputStream(conn); - synchronized (context) { - if (context.aborted) { - return; - } - context.currentInputStream = inStream; - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, conn.getContentLength())); - byte[] buffer = new byte[1024]; - int bytesRead = 0; - // write bytes to file - while ((bytesRead = inStream.read(buffer)) > 0) { - out.write(buffer, 0, bytesRead); - } - responseString = out.toString("UTF-8"); - } finally { - context.currentInputStream = null; - safeClose(inStream); - } - - Log.d(LOG_TAG, "got response from server"); - Log.d(LOG_TAG, responseString.substring(0, Math.min(256, responseString.length()))); - - // send request and retrieve response - result.setResponseCode(responseCode); - result.setResponse(responseString); - - context.sendPluginResult(new PluginResult(PluginResult.Status.OK, result.toJSONObject())); - } catch (FileNotFoundException e) { - JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn); - Log.e(LOG_TAG, error.toString(), e); - context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - } catch (IOException e) { - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); - Log.e(LOG_TAG, error.toString(), e); - Log.e(LOG_TAG, "Failed after uploading " + totalBytes + " of " + fixedLength + " bytes."); - context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - context.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); - } catch (Throwable t) { - // Shouldn't happen, but will - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); - Log.e(LOG_TAG, error.toString(), t); - context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - } finally { - synchronized (activeRequests) { - activeRequests.remove(objectId); - } - - if (conn != null) { - // Revert back to the proper verifier and socket factories - // Revert back to the proper verifier and socket factories - if (trustEveryone && useHttps) { - HttpsURLConnection https = (HttpsURLConnection) conn; - https.setHostnameVerifier(oldHostnameVerifier); - https.setSSLSocketFactory(oldSocketFactory); - } - } - } - } - }); - } - - private static void safeClose(Closeable stream) { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - } - } - } - - private static TrackingInputStream getInputStream(URLConnection conn) throws IOException { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - return new DoneHandlerInputStream(conn.getInputStream()); - } - String encoding = conn.getContentEncoding(); - if (encoding != null && encoding.equalsIgnoreCase("gzip")) { - return new TrackingGZIPInputStream(new ExposedGZIPInputStream(conn.getInputStream())); - } - return new TrackingHTTPInputStream(conn.getInputStream()); - } - - // always verify the host - don't check for certificate - private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { - public boolean verify(String hostname, SSLSession session) { - return true; - } - }; - // Create a trust manager that does not validate certificate chains - private static final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[] {}; - } - - public void checkClientTrusted(X509Certificate[] chain, - String authType) throws CertificateException { - } - - public void checkServerTrusted(X509Certificate[] chain, - String authType) throws CertificateException { - } - } }; - - /** - * This function will install a trust manager that will blindly trust all SSL - * certificates. The reason this code is being added is to enable developers - * to do development using self signed SSL certificates on their web server. - * - * The standard HttpsURLConnection class will throw an exception on self - * signed certificates if this code is not run. - */ - private static SSLSocketFactory trustAllHosts(HttpsURLConnection connection) { - // Install the all-trusting trust manager - SSLSocketFactory oldFactory = connection.getSSLSocketFactory(); - try { - // Install our all trusting manager - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - SSLSocketFactory newFactory = sc.getSocketFactory(); - connection.setSSLSocketFactory(newFactory); - } catch (Exception e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - return oldFactory; - } - - private static JSONObject createFileTransferError(int errorCode, String source, String target, URLConnection connection) { - - int httpStatus = 0; - StringBuilder bodyBuilder = new StringBuilder(); - String body = null; - if (connection != null) { - try { - if (connection instanceof HttpURLConnection) { - httpStatus = ((HttpURLConnection)connection).getResponseCode(); - InputStream err = ((HttpURLConnection) connection).getErrorStream(); - if(err != null) - { - BufferedReader reader = new BufferedReader(new InputStreamReader(err, "UTF-8")); - String line = reader.readLine(); - while(line != null) - { - bodyBuilder.append(line); - line = reader.readLine(); - if(line != null) - bodyBuilder.append('\n'); - } - body = bodyBuilder.toString(); - } - } - } catch (IOException e) { - Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e); - } - } - - return createFileTransferError(errorCode, source, target, body, httpStatus); - } - - /** - * Create an error object based on the passed in errorCode - * @param errorCode the error - * @return JSONObject containing the error - */ - private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus) { - JSONObject error = null; - try { - error = new JSONObject(); - error.put("code", errorCode); - error.put("source", source); - error.put("target", target); - if(body != null) - { - error.put("body", body); - } - if (httpStatus != null) { - error.put("http_status", httpStatus); - } - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - return error; - } - - /** - * Convenience method to read a parameter from the list of JSON args. - * @param args the args passed to the Plugin - * @param position the position to retrieve the arg from - * @param defaultString the default to be used if the arg does not exist - * @return String with the retrieved value - */ - private static String getArgument(JSONArray args, int position, String defaultString) { - String arg = defaultString; - if (args.length() > position) { - arg = args.optString(position); - if (arg == null || "null".equals(arg)) { - arg = defaultString; - } - } - return arg; - } - - /** - * Downloads a file form a given URL and saves it to the specified directory. - * - * @param source URL of the server to receive the file - * @param target Full path of the file on the file system - */ - private void download(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException { - Log.d(LOG_TAG, "download " + source + " to " + target); - - final boolean trustEveryone = args.optBoolean(2); - final String objectId = args.getString(3); - final JSONObject headers = args.optJSONObject(4); - - final URL url; - try { - url = new URL(source); - } catch (MalformedURLException e) { - JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0); - Log.e(LOG_TAG, error.toString(), e); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - return; - } - final boolean useHttps = url.getProtocol().equals("https"); - - if (!Config.isUrlWhiteListed(source)) { - Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'"); - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - return; - } - - - final RequestContext context = new RequestContext(source, target, callbackContext); - synchronized (activeRequests) { - activeRequests.put(objectId, context); - } - - cordova.getThreadPool().execute(new Runnable() { - public void run() { - if (context.aborted) { - return; - } - URLConnection connection = null; - HostnameVerifier oldHostnameVerifier = null; - SSLSocketFactory oldSocketFactory = null; - File file = null; - PluginResult result = null; - - try { - file = getFileFromPath(target); - context.targetFile = file; - // create needed directories - file.getParentFile().mkdirs(); - - // connect to server - // Open a HTTP connection to the URL based on protocol - if (useHttps) { - // Using standard HTTPS connection. Will not allow self signed certificate - if (!trustEveryone) { - connection = (HttpsURLConnection) httpClient.open(url); - } - // Use our HTTPS connection that blindly trusts everyone. - // This should only be used in debug environments - else { - // Setup the HTTPS connection class to trust everyone - HttpsURLConnection https = (HttpsURLConnection) httpClient.open(url); - oldSocketFactory = trustAllHosts(https); - // Save the current hostnameVerifier - oldHostnameVerifier = https.getHostnameVerifier(); - // Setup the connection not to verify hostnames - https.setHostnameVerifier(DO_NOT_VERIFY); - connection = https; - } - } - // Return a standard HTTP connection - else { - connection = httpClient.open(url); - - } - - if (connection instanceof HttpURLConnection) { - ((HttpURLConnection)connection).setRequestMethod("GET"); - } - - //Add cookie support - String cookie = CookieManager.getInstance().getCookie(source); - if(cookie != null) - { - connection.setRequestProperty("cookie", cookie); - } - - // This must be explicitly set for gzip progress tracking to work. - connection.setRequestProperty("Accept-Encoding", "gzip"); - - // Handle the other headers - if (headers != null) { - addHeadersToRequest(connection, headers); - } - - connection.connect(); - - Log.d(LOG_TAG, "Download file:" + url); - - FileProgressResult progress = new FileProgressResult(); - if (connection.getContentEncoding() == null || connection.getContentEncoding().equalsIgnoreCase("gzip")) { - // Only trust content-length header if we understand - // the encoding -- identity or gzip - progress.setLengthComputable(true); - progress.setTotal(connection.getContentLength()); - } - - FileOutputStream outputStream = null; - TrackingInputStream inputStream = null; - - try { - inputStream = getInputStream(connection); - outputStream = new FileOutputStream(file); - synchronized (context) { - if (context.aborted) { - return; - } - context.currentInputStream = inputStream; - } - - // write bytes to file - byte[] buffer = new byte[MAX_BUFFER_SIZE]; - int bytesRead = 0; - while ((bytesRead = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, bytesRead); - // Send a progress event. - progress.setLoaded(inputStream.getTotalRawBytesRead()); - PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); - progressResult.setKeepCallback(true); - context.sendPluginResult(progressResult); - } - } finally { - context.currentInputStream = null; - safeClose(inputStream); - safeClose(outputStream); - } - - Log.d(LOG_TAG, "Saved file: " + target); - - // create FileEntry object - JSONObject fileEntry = FileUtils.getEntry(file); - - result = new PluginResult(PluginResult.Status.OK, fileEntry); - } catch (FileNotFoundException e) { - JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection); - Log.e(LOG_TAG, error.toString(), e); - result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } catch (IOException e) { - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection); - Log.e(LOG_TAG, error.toString(), e); - result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - result = new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } catch (Throwable e) { - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection); - Log.e(LOG_TAG, error.toString(), e); - result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } finally { - synchronized (activeRequests) { - activeRequests.remove(objectId); - } - - if (connection != null) { - // Revert back to the proper verifier and socket factories - if (trustEveryone && useHttps) { - HttpsURLConnection https = (HttpsURLConnection) connection; - https.setHostnameVerifier(oldHostnameVerifier); - https.setSSLSocketFactory(oldSocketFactory); - } - } - - if (result == null) { - result = new PluginResult(PluginResult.Status.ERROR, createFileTransferError(CONNECTION_ERR, source, target, connection)); - } - // Remove incomplete download. - if (result.getStatus() != PluginResult.Status.OK.ordinal() && file != null) { - file.delete(); - } - context.sendPluginResult(result); - } - } - }); - } - - /** - * Get an input stream based on file path or content:// uri - * - * @param path foo - * @return an input stream - * @throws FileNotFoundException - */ - private InputStream getPathFromUri(String path) throws FileNotFoundException { - if (path.startsWith("content:")) { - Uri uri = Uri.parse(path); - return cordova.getActivity().getContentResolver().openInputStream(uri); - } - else if (path.startsWith("file://")) { - int question = path.indexOf("?"); - if (question == -1) { - return new FileInputStream(path.substring(7)); - } else { - return new FileInputStream(path.substring(7, question)); - } - } - else { - return new FileInputStream(path); - } - } - - /** - * Get a File object from the passed in path - * - * @param path file path - * @return file object - */ - private File getFileFromPath(String path) throws FileNotFoundException { - File file; - String prefix = "file://"; - - if (path.startsWith(prefix)) { - file = new File(path.substring(prefix.length())); - } else { - file = new File(path); - } - - if (file.getParent() == null) { - throw new FileNotFoundException(); - } - - return file; - } - - /** - * Abort an ongoing upload or download. - */ - private void abort(String objectId) { - final RequestContext context; - synchronized (activeRequests) { - context = activeRequests.remove(objectId); - } - if (context != null) { - File file = context.targetFile; - if (file != null) { - file.delete(); - } - // Trigger the abort callback immediately to minimize latency between it and abort() being called. - JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, null, -1); - synchronized (context) { - context.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error)); - context.aborted = true; - } - // Closing the streams can block, so execute on a background thread. - cordova.getThreadPool().execute(new Runnable() { - public void run() { - synchronized (context) { - safeClose(context.currentInputStream); - safeClose(context.currentOutputStream); - } - } - }); - } - } -} diff --git a/framework/src/org/apache/cordova/FileUploadResult.java b/framework/src/org/apache/cordova/FileUploadResult.java deleted file mode 100644 index b556869e..00000000 --- a/framework/src/org/apache/cordova/FileUploadResult.java +++ /dev/null @@ -1,73 +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 org.json.JSONException; -import org.json.JSONObject; - -/** - * Encapsulates the result and/or status of uploading a file to a remote server. - */ -public class FileUploadResult { - - private long bytesSent = 0; // bytes sent - private int responseCode = -1; // HTTP response code - private String response = null; // HTTP response - private String objectId = null; // FileTransfer object id - - public long getBytesSent() { - return bytesSent; - } - - public void setBytesSent(long bytes) { - this.bytesSent = bytes; - } - - public int getResponseCode() { - return responseCode; - } - - public void setResponseCode(int responseCode) { - this.responseCode = responseCode; - } - - public String getResponse() { - return response; - } - - public void setResponse(String response) { - this.response = response; - } - - public String getObjectId() { - return objectId; - } - - public void setObjectId(String objectId) { - this.objectId = objectId; - } - - public JSONObject toJSONObject() throws JSONException { - return new JSONObject( - "{bytesSent:" + bytesSent + - ",responseCode:" + responseCode + - ",response:" + JSONObject.quote(response) + - ",objectId:" + JSONObject.quote(objectId) + "}"); - } -} diff --git a/framework/src/org/apache/cordova/FileUtils.java b/framework/src/org/apache/cordova/FileUtils.java deleted file mode 100755 index e62fc4a1..00000000 --- a/framework/src/org/apache/cordova/FileUtils.java +++ /dev/null @@ -1,1024 +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 android.os.Environment; -import android.provider.MediaStore; -import android.util.Log; - -import org.apache.commons.codec.binary.Base64; -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.DataResource; -import org.apache.cordova.api.PluginResult; -import org.apache.cordova.file.EncodingException; -import org.apache.cordova.file.FileExistsException; -import org.apache.cordova.file.InvalidModificationException; -import org.apache.cordova.file.NoModificationAllowedException; -import org.apache.cordova.file.TypeMismatchException; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.net.MalformedURLException; -import java.nio.channels.FileChannel; - -/** - * This class provides SD card file and directory services to JavaScript. - * Only files on the SD card can be accessed. - */ -public class FileUtils extends CordovaPlugin { - private static final String LOG_TAG = "FileUtils"; - - public static int NOT_FOUND_ERR = 1; - public static int SECURITY_ERR = 2; - public static int ABORT_ERR = 3; - - public static int NOT_READABLE_ERR = 4; - public static int ENCODING_ERR = 5; - public static int NO_MODIFICATION_ALLOWED_ERR = 6; - public static int INVALID_STATE_ERR = 7; - public static int SYNTAX_ERR = 8; - public static int INVALID_MODIFICATION_ERR = 9; - public static int QUOTA_EXCEEDED_ERR = 10; - public static int TYPE_MISMATCH_ERR = 11; - public static int PATH_EXISTS_ERR = 12; - - public static int TEMPORARY = 0; - public static int PERSISTENT = 1; - public static int RESOURCE = 2; - public static int APPLICATION = 3; - - /** - * Constructor. - */ - public FileUtils() { - } - - /** - * Executes the request and returns whether the action was valid. - * - * @param action The action to execute. - * @param args JSONArray of arguments for the plugin. - * @param callbackContext The callback context used when calling back into JavaScript. - * @return True if the action was valid, false otherwise. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - try { - if (action.equals("testSaveLocationExists")) { - boolean b = DirectoryManager.testSaveLocationExists(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b)); - } - else if (action.equals("getFreeDiskSpace")) { - long l = DirectoryManager.getFreeDiskSpace(false); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l)); - } - else if (action.equals("testFileExists")) { - boolean b = DirectoryManager.testFileExists(args.getString(0)); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b)); - } - else if (action.equals("testDirectoryExists")) { - boolean b = DirectoryManager.testFileExists(args.getString(0)); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b)); - } - else if (action.equals("readAsText")) { - String encoding = args.getString(1); - int start = args.getInt(2); - int end = args.getInt(3); - - this.readFileAs(args.getString(0), start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING); - } - else if (action.equals("readAsDataURL")) { - int start = args.getInt(1); - int end = args.getInt(2); - - this.readFileAs(args.getString(0), start, end, callbackContext, null, -1); - } - else if (action.equals("readAsArrayBuffer")) { - int start = args.getInt(1); - int end = args.getInt(2); - - this.readFileAs(args.getString(0), start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER); - } - else if (action.equals("readAsBinaryString")) { - int start = args.getInt(1); - int end = args.getInt(2); - - this.readFileAs(args.getString(0), start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING); - } - else if (action.equals("write")) { - long fileSize = this.write(args.getString(0), args.getString(1), args.getInt(2)); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize)); - } - else if (action.equals("truncate")) { - long fileSize = this.truncateFile(args.getString(0), args.getLong(1)); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize)); - } - else if (action.equals("requestFileSystem")) { - long size = args.optLong(1); - if (size != 0 && size > (DirectoryManager.getFreeDiskSpace(true) * 1024)) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR)); - } else { - JSONObject obj = requestFileSystem(args.getInt(0)); - callbackContext.success(obj); - } - } - else if (action.equals("resolveLocalFileSystemURI")) { - JSONObject obj = resolveLocalFileSystemURI(args.getString(0)); - callbackContext.success(obj); - } - else if (action.equals("getMetadata")) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getMetadata(args.getString(0)))); - } - else if (action.equals("getFileMetadata")) { - JSONObject obj = getFileMetadata(args.getString(0)); - callbackContext.success(obj); - } - else if (action.equals("getParent")) { - JSONObject obj = getParent(args.getString(0)); - callbackContext.success(obj); - } - else if (action.equals("getDirectory")) { - JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), true); - callbackContext.success(obj); - } - else if (action.equals("getFile")) { - JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), false); - callbackContext.success(obj); - } - else if (action.equals("remove")) { - boolean success; - - success = remove(args.getString(0)); - - if (success) { - notifyDelete(args.getString(0)); - callbackContext.success(); - } else { - callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR); - } - } - else if (action.equals("removeRecursively")) { - boolean success = removeRecursively(args.getString(0)); - if (success) { - callbackContext.success(); - } else { - callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR); - } - } - else if (action.equals("moveTo")) { - JSONObject entry = transferTo(args.getString(0), args.getString(1), args.getString(2), true); - callbackContext.success(entry); - } - else if (action.equals("copyTo")) { - JSONObject entry = transferTo(args.getString(0), args.getString(1), args.getString(2), false); - callbackContext.success(entry); - } - else if (action.equals("readEntries")) { - JSONArray entries = readEntries(args.getString(0)); - callbackContext.success(entries); - } - else { - return false; - } - } catch (FileNotFoundException e) { - callbackContext.error(FileUtils.NOT_FOUND_ERR); - } catch (FileExistsException e) { - callbackContext.error(FileUtils.PATH_EXISTS_ERR); - } catch (NoModificationAllowedException e) { - callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR); - } catch (InvalidModificationException e) { - callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR); - } catch (MalformedURLException e) { - callbackContext.error(FileUtils.ENCODING_ERR); - } catch (IOException e) { - callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR); - } catch (EncodingException e) { - callbackContext.error(FileUtils.ENCODING_ERR); - } catch (TypeMismatchException e) { - callbackContext.error(FileUtils.TYPE_MISMATCH_ERR); - } - return true; - } - - /** - * Need to check to see if we need to clean up the content store - * - * @param filePath the path to check - */ - private void notifyDelete(String filePath) { - String newFilePath = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "FileUtils.notifyDelete").getRealFile().getPath(); - try { - this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - MediaStore.Images.Media.DATA + " = ?", - new String[] { newFilePath }); - } catch (UnsupportedOperationException t) { - // Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator. - // The ContentResolver applies only when the file was registered in the - // first case, which is generally only the case with images. - } - } - - /** - * Allows the user to look up the Entry for a file or directory referred to by a local URI. - * - * @param url of the file/directory to look up - * @return a JSONObject representing a Entry from the filesystem - * @throws MalformedURLException if the url is not valid - * @throws FileNotFoundException if the file does not exist - * @throws IOException if the user can't read the file - * @throws JSONException - */ - private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException { - File fp = DataResource.initiateNewDataRequestForUri(url, webView.pluginManager, cordova, "FileUtils.resolveLocalFileSystemURI").getRealFile(); - - if (fp == null || !fp.exists()) { - throw new FileNotFoundException(); - } - if (!fp.canRead()) { - throw new IOException(); - } - return getEntry(fp); - } - - /** - * Read the list of files from this directory. - * - * @param fileName the directory to read from - * @return a JSONArray containing JSONObjects that represent Entry objects. - * @throws FileNotFoundException if the directory is not found. - * @throws JSONException - */ - private JSONArray readEntries(String fileName) throws FileNotFoundException, JSONException { - File fp = DataResource.initiateNewDataRequestForUri(fileName, webView.pluginManager, cordova, "FileUtils.readEntries").getRealFile(); - - if (fp == null || !fp.exists()) { - // The directory we are listing doesn't exist so we should fail. - throw new FileNotFoundException(); - } - - JSONArray entries = new JSONArray(); - - if (fp.isDirectory()) { - File[] files = fp.listFiles(); - for (int i = 0; i < files.length; i++) { - if (files[i].canRead()) { - entries.put(getEntry(files[i])); - } - } - } - - return entries; - } - - /** - * A setup method that handles the move/copy of files/directories - * - * @param fileName to be copied/moved - * @param newParent is the location where the file will be copied/moved to - * @param newName for the file directory to be called, if null use existing file name - * @param move if false do a copy, if true do a move - * @return a Entry object - * @throws NoModificationAllowedException - * @throws IOException - * @throws InvalidModificationException - * @throws EncodingException - * @throws JSONException - * @throws FileExistsException - */ - private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException { - DataResource dataResourceFrom = DataResource.initiateNewDataRequestForUri(fileName, webView.pluginManager, cordova, "FileUtils.transferTo"); - String newFileName = dataResourceFrom.getRealFile().getPath(); - DataResource dataResourceTo = DataResource.initiateNewDataRequestForUri(newParent, webView.pluginManager, cordova, "FileUtils.transferTo"); - newParent = dataResourceTo.getRealFile().getPath(); - - // Check for invalid file name - if (newName != null && newName.contains(":")) { - throw new EncodingException("Bad file name"); - } - - File source = new File(newFileName); - - if (!source.exists()) { - // The file/directory we are copying doesn't exist so we should fail. - throw new FileNotFoundException("The source does not exist"); - } - - File destinationDir = new File(newParent); - if (!destinationDir.exists()) { - // The destination does not exist so we should fail. - throw new FileNotFoundException("The source does not exist"); - } - - // Figure out where we should be copying to - File destination = createDestination(newName, source, destinationDir); - - //Log.d(LOG_TAG, "Source: " + source.getAbsolutePath()); - //Log.d(LOG_TAG, "Destin: " + destination.getAbsolutePath()); - - // Check to see if source and destination are the same file - if (source.getAbsolutePath().equals(destination.getAbsolutePath())) { - throw new InvalidModificationException("Can't copy a file onto itself"); - } - - if (source.isDirectory()) { - if (move) { - return moveDirectory(source, destination); - } else { - return copyDirectory(source, destination); - } - } else { - if (move) { - JSONObject newFileEntry = moveFile(source, destination); - - // If we've moved a file given its content URI, we need to clean up. - if (fileName.startsWith("content://")) { - notifyDelete(fileName); - } - - return newFileEntry; - } else { - return copyFile(source, destination); - } - } - } - - /** - * Creates the destination File object based on name passed in - * - * @param newName for the file directory to be called, if null use existing file name - * @param fp represents the source file - * @param destination represents the destination file - * @return a File object that represents the destination - */ - private File createDestination(String newName, File fp, File destination) { - File destFile = null; - - // I know this looks weird but it is to work around a JSON bug. - if ("null".equals(newName) || "".equals(newName)) { - newName = null; - } - - if (newName != null) { - destFile = new File(destination.getAbsolutePath() + File.separator + newName); - } else { - destFile = new File(destination.getAbsolutePath() + File.separator + fp.getName()); - } - return destFile; - } - - /** - * Copy a file - * - * @param srcFile file to be copied - * @param destFile destination to be copied to - * @return a FileEntry object - * @throws IOException - * @throws InvalidModificationException - * @throws JSONException - */ - private JSONObject copyFile(File srcFile, File destFile) throws IOException, InvalidModificationException, JSONException { - // Renaming a file to an existing directory should fail - if (destFile.exists() && destFile.isDirectory()) { - throw new InvalidModificationException("Can't rename a file to a directory"); - } - - copyAction(srcFile, destFile); - - return getEntry(destFile); - } - - /** - * Moved this code into it's own method so moveTo could use it when the move is across file systems - */ - private void copyAction(File srcFile, File destFile) - throws FileNotFoundException, IOException { - FileInputStream istream = new FileInputStream(srcFile); - FileOutputStream ostream = new FileOutputStream(destFile); - FileChannel input = istream.getChannel(); - FileChannel output = ostream.getChannel(); - - try { - input.transferTo(0, input.size(), output); - } finally { - istream.close(); - ostream.close(); - input.close(); - output.close(); - } - } - - /** - * Copy a directory - * - * @param srcDir directory to be copied - * @param destinationDir destination to be copied to - * @return a DirectoryEntry object - * @throws JSONException - * @throws IOException - * @throws NoModificationAllowedException - * @throws InvalidModificationException - */ - private JSONObject copyDirectory(File srcDir, File destinationDir) throws JSONException, IOException, NoModificationAllowedException, InvalidModificationException { - // Renaming a file to an existing directory should fail - if (destinationDir.exists() && destinationDir.isFile()) { - throw new InvalidModificationException("Can't rename a file to a directory"); - } - - // Check to make sure we are not copying the directory into itself - if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) { - throw new InvalidModificationException("Can't copy itself into itself"); - } - - // See if the destination directory exists. If not create it. - if (!destinationDir.exists()) { - if (!destinationDir.mkdir()) { - // If we can't create the directory then fail - throw new NoModificationAllowedException("Couldn't create the destination directory"); - } - } - - for (File file : srcDir.listFiles()) { - if (file.isDirectory()) { - copyDirectory(file, destinationDir); - } else { - File destination = new File(destinationDir.getAbsoluteFile() + File.separator + file.getName()); - copyFile(file, destination); - } - } - - return getEntry(destinationDir); - } - - /** - * Check to see if the user attempted to copy an entry into its parent without changing its name, - * or attempted to copy a directory into a directory that it contains directly or indirectly. - * - * @param srcDir - * @param destinationDir - * @return - */ - private boolean isCopyOnItself(String src, String dest) { - - // This weird test is to determine if we are copying or moving a directory into itself. - // Copy /sdcard/myDir to /sdcard/myDir-backup is okay but - // Copy /sdcard/myDir to /sdcard/myDir/backup should throw an INVALID_MODIFICATION_ERR - if (dest.startsWith(src) && dest.indexOf(File.separator, src.length() - 1) != -1) { - return true; - } - - return false; - } - - /** - * Move a file - * - * @param srcFile file to be copied - * @param destFile destination to be copied to - * @return a FileEntry object - * @throws IOException - * @throws InvalidModificationException - * @throws JSONException - */ - private JSONObject moveFile(File srcFile, File destFile) throws IOException, JSONException, InvalidModificationException { - // Renaming a file to an existing directory should fail - if (destFile.exists() && destFile.isDirectory()) { - throw new InvalidModificationException("Can't rename a file to a directory"); - } - - // Try to rename the file - if (!srcFile.renameTo(destFile)) { - // Trying to rename the file failed. Possibly because we moved across file system on the device. - // Now we have to do things the hard way - // 1) Copy all the old file - // 2) delete the src file - copyAction(srcFile, destFile); - if (destFile.exists()) { - srcFile.delete(); - } else { - throw new IOException("moved failed"); - } - } - - return getEntry(destFile); - } - - /** - * Move a directory - * - * @param srcDir directory to be copied - * @param destinationDir destination to be copied to - * @return a DirectoryEntry object - * @throws JSONException - * @throws IOException - * @throws InvalidModificationException - * @throws NoModificationAllowedException - * @throws FileExistsException - */ - private JSONObject moveDirectory(File srcDir, File destinationDir) throws IOException, JSONException, InvalidModificationException, NoModificationAllowedException, FileExistsException { - // Renaming a file to an existing directory should fail - if (destinationDir.exists() && destinationDir.isFile()) { - throw new InvalidModificationException("Can't rename a file to a directory"); - } - - // Check to make sure we are not copying the directory into itself - if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) { - throw new InvalidModificationException("Can't move itself into itself"); - } - - // If the destination directory already exists and is empty then delete it. This is according to spec. - if (destinationDir.exists()) { - if (destinationDir.list().length > 0) { - throw new InvalidModificationException("directory is not empty"); - } - } - - // Try to rename the directory - if (!srcDir.renameTo(destinationDir)) { - // Trying to rename the directory failed. Possibly because we moved across file system on the device. - // Now we have to do things the hard way - // 1) Copy all the old files - // 2) delete the src directory - copyDirectory(srcDir, destinationDir); - if (destinationDir.exists()) { - removeDirRecursively(srcDir); - } else { - throw new IOException("moved failed"); - } - } - - return getEntry(destinationDir); - } - - /** - * Deletes a directory and all of its contents, if any. In the event of an error - * [e.g. trying to delete a directory that contains a file that cannot be removed], - * some of the contents of the directory may be deleted. - * It is an error to attempt to delete the root directory of a filesystem. - * - * @param filePath the directory to be removed - * @return a boolean representing success of failure - * @throws FileExistsException - */ - private boolean removeRecursively(String filePath) throws FileExistsException { - File fp = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "FileUtils.readEntries").getRealFile(); - - // You can't delete the root directory. - if (atRootDirectory(filePath)) { - return false; - } - - return removeDirRecursively(fp); - } - - /** - * Loops through a directory deleting all the files. - * - * @param directory to be removed - * @return a boolean representing success of failure - * @throws FileExistsException - */ - private boolean removeDirRecursively(File directory) throws FileExistsException { - if (directory.isDirectory()) { - for (File file : directory.listFiles()) { - removeDirRecursively(file); - } - } - - if (!directory.delete()) { - throw new FileExistsException("could not delete: " + directory.getName()); - } else { - return true; - } - } - - /** - * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty. - * It is an error to attempt to delete the root directory of a filesystem. - * - * @param filePath file or directory to be removed - * @return a boolean representing success of failure - * @throws NoModificationAllowedException - * @throws InvalidModificationException - */ - private boolean remove(String filePath) throws NoModificationAllowedException, InvalidModificationException { - File fp = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "FileUtils.readEntries").getRealFile(); - - // You can't delete the root directory. - if (atRootDirectory(filePath)) { - throw new NoModificationAllowedException("You can't delete the root directory"); - } - - // You can't delete a directory that is not empty - if (fp.isDirectory() && fp.list().length > 0) { - throw new InvalidModificationException("You can't delete a directory that is not empty."); - } - - return fp.delete(); - } - - /** - * Creates or looks up a file. - * - * @param dirPath base directory - * @param fileName file/directory to lookup or create - * @param options specify whether to create or not - * @param directory if true look up directory, if false look up file - * @return a Entry object - * @throws FileExistsException - * @throws IOException - * @throws TypeMismatchException - * @throws EncodingException - * @throws JSONException - */ - private JSONObject getFile(String dirPath, String fileName, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException { - boolean create = false; - boolean exclusive = false; - if (options != null) { - create = options.optBoolean("create"); - if (create) { - exclusive = options.optBoolean("exclusive"); - } - } - - // Check for a ":" character in the file to line up with BB and iOS - if (fileName.contains(":")) { - throw new EncodingException("This file has a : in it's name"); - } - - String filePath = getFullFilePath(dirPath, fileName); - File fp = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "FileUtils.getFile").getRealFile(); - - if (create) { - if (exclusive && fp.exists()) { - throw new FileExistsException("create/exclusive fails"); - } - if (directory) { - fp.mkdir(); - } else { - fp.createNewFile(); - } - if (!fp.exists()) { - throw new FileExistsException("create fails"); - } - } - else { - if (!fp.exists()) { - throw new FileNotFoundException("path does not exist"); - } - if (directory) { - if (fp.isFile()) { - throw new TypeMismatchException("path doesn't exist or is file"); - } - } else { - if (fp.isDirectory()) { - throw new TypeMismatchException("path doesn't exist or is directory"); - } - } - } - - // Return the directory - return getEntry(fp); - } - - /** - * If the path starts with a '/' just return that file object. If not construct the file - * object from the path passed in and the file name. - * - * @param dirPath root directory - * @param fileName new file name - * @return - */ - private String getFullFilePath(String dirPath, String fileName) { - if (fileName.startsWith("/")) { - return fileName; - } else { - DataResource dataResource = DataResource.initiateNewDataRequestForUri(dirPath, webView.pluginManager, cordova, "FileUtils.getFullFilePath"); - dirPath = dataResource.getRealFile().getPath(); - return dirPath + File.separator + fileName; - } - } - - /** - * Look up the parent DirectoryEntry containing this Entry. - * If this Entry is the root of its filesystem, its parent is itself. - * - * @param filePath - * @return - * @throws JSONException - */ - private JSONObject getParent(String filePath) throws JSONException { - DataResource dataResource = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "FileUtils.getParent"); - filePath = dataResource.getRealFile().getPath(); - - if (atRootDirectory(filePath)) { - return getEntry(filePath); - } - return getEntry(dataResource.getRealFile().getParent()); - } - - /** - * Checks to see if we are at the root directory. Useful since we are - * not allow to delete this directory. - * - * @param filePath to directory - * @return true if we are at the root, false otherwise. - */ - private boolean atRootDirectory(String filePath) { - filePath = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "FileUtils.atRootDirectory").getRealFile().getPath(); - - if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache") || - filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath()) || - filePath.equals("/data/data/" + cordova.getActivity().getPackageName())) { - return true; - } - return false; - } - - /** - * Look up metadata about this entry. - * - * @param filePath to entry - * @return a long - * @throws FileNotFoundException - */ - private long getMetadata(String filePath) throws FileNotFoundException { - File file = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "FileUtils.getMetadata").getRealFile(); - - if (file == null || !file.exists()) { - throw new FileNotFoundException("Failed to find file in getMetadata"); - } - - return file.lastModified(); - } - - /** - * Returns a File that represents the current state of the file that this FileEntry represents. - * - * @param filePath to entry - * @return returns a JSONObject represent a W3C File object - * @throws FileNotFoundException - * @throws JSONException - */ - private JSONObject getFileMetadata(String filePath) throws FileNotFoundException, JSONException { - DataResource dataResource = DataResource.initiateNewDataRequestForUri(filePath, webView.pluginManager, cordova, "FileUtils.getMetadata"); - File file = dataResource.getRealFile(); - - if (file == null || !file.exists()) { - throw new FileNotFoundException("File: " + filePath + " does not exist."); - } - - JSONObject metadata = new JSONObject(); - metadata.put("size", file.length()); - metadata.put("type", dataResource.getMimeType()); - metadata.put("name", file.getName()); - metadata.put("fullPath", filePath); - metadata.put("lastModifiedDate", file.lastModified()); - - return metadata; - } - - /** - * Requests a filesystem in which to store application data. - * - * @param type of file system requested - * @return a JSONObject representing the file system - * @throws IOException - * @throws JSONException - */ - private JSONObject requestFileSystem(int type) throws IOException, JSONException { - JSONObject fs = new JSONObject(); - if (type == TEMPORARY) { - File fp; - fs.put("name", "temporary"); - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - fp = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + - "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/"); - // Create the cache dir if it doesn't exist. - fp.mkdirs(); - fs.put("root", getEntry(Environment.getExternalStorageDirectory().getAbsolutePath() + - "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/")); - } else { - fp = new File("/data/data/" + cordova.getActivity().getPackageName() + "/cache/"); - // Create the cache dir if it doesn't exist. - fp.mkdirs(); - fs.put("root", getEntry("/data/data/" + cordova.getActivity().getPackageName() + "/cache/")); - } - } - else if (type == PERSISTENT) { - fs.put("name", "persistent"); - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - fs.put("root", getEntry(Environment.getExternalStorageDirectory())); - } else { - fs.put("root", getEntry("/data/data/" + cordova.getActivity().getPackageName())); - } - } - else { - throw new IOException("No filesystem of type requested"); - } - - return fs; - } - - /** - * Returns a JSON object representing the given File. - * - * @param file the File to convert - * @return a JSON representation of the given File - * @throws JSONException - */ - public static JSONObject getEntry(File file) throws JSONException { - JSONObject entry = new JSONObject(); - - entry.put("isFile", file.isFile()); - entry.put("isDirectory", file.isDirectory()); - entry.put("name", file.getName()); - entry.put("fullPath", "file://" + file.getAbsolutePath()); - // The file system can't be specified, as it would lead to an infinite loop. - // entry.put("filesystem", null); - - return entry; - } - - /** - * Returns a JSON Object representing a directory on the device's file system - * - * @param path to the directory - * @return - * @throws JSONException - */ - private JSONObject getEntry(String path) throws JSONException { - return getEntry(new File(path)); - } - - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - /** - * Read the contents of a file. - * This is done in a background thread; the result is sent to the callback. - * - * @param filename The name of the file. - * @param start Start position in the file. - * @param end End position to stop at (exclusive). - * @param callbackContext The context through which to send the result. - * @param encoding The encoding to return contents as. Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets) - * @param resultType The desired type of data to send to the callback. - * @return Contents of file. - */ - public void readFileAs(final String filename, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) { - this.cordova.getThreadPool().execute(new Runnable() { - public void run() { - try { - DataResource dataResource = DataResource.initiateNewDataRequestForUri(filename, webView.pluginManager, cordova, "FileUtils.readFileAs"); - byte[] bytes = readAsBinaryHelper(dataResource.getInputStream(), start, end); - - PluginResult result; - switch (resultType) { - case PluginResult.MESSAGE_TYPE_STRING: - result = new PluginResult(PluginResult.Status.OK, new String(bytes, encoding)); - break; - case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: - result = new PluginResult(PluginResult.Status.OK, bytes); - break; - case PluginResult.MESSAGE_TYPE_BINARYSTRING: - result = new PluginResult(PluginResult.Status.OK, bytes, true); - break; - default: // Base64. - String contentType = dataResource.getMimeType(); - byte[] base64 = Base64.encodeBase64(bytes); - String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII"); - result = new PluginResult(PluginResult.Status.OK, s); - } - - callbackContext.sendPluginResult(result); - } catch (FileNotFoundException e) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR)); - } catch (IOException e) { - Log.d(LOG_TAG, e.getLocalizedMessage()); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR)); - } - } - }); - } - - /** - * Read the contents of a file as binary. - * This is done synchronously; the result is returned. - * - * @param filename The name of the file. - * @param start Start position in the file. - * @param end End position to stop at (exclusive). - * @return Contents of the file as a byte[]. - * @throws IOException - */ - private byte[] readAsBinaryHelper(InputStream inputStream, int start, int end) throws IOException { - int numBytesToRead = end - start; - byte[] bytes = new byte[numBytesToRead]; - int numBytesRead = 0; - - if (start > 0) { - inputStream.skip(start); - } - - while (numBytesToRead > 0 && (numBytesRead = inputStream.read(bytes, numBytesRead, numBytesToRead)) >= 0) { - numBytesToRead -= numBytesRead; - } - - return bytes; - } - - /** - * Write contents of file. - * - * @param filename The name of the file. - * @param data The contents of the file. - * @param offset The position to begin writing the file. - * @throws FileNotFoundException, IOException - * @throws NoModificationAllowedException - */ - /**/ - public long write(String filename, String data, int offset) throws FileNotFoundException, IOException, NoModificationAllowedException { - if (filename.startsWith("content://")) { - throw new NoModificationAllowedException("Couldn't write to file given its content URI"); - } - - DataResource dataResource = DataResource.initiateNewDataRequestForUri(filename, webView.pluginManager, cordova, "FileUtils.write"); - filename = dataResource.getRealFile().getPath(); - - boolean append = false; - if (offset > 0) { - this.truncateFile(filename, offset); - append = true; - } - - byte[] rawData = data.getBytes(); - ByteArrayInputStream in = new ByteArrayInputStream(rawData); - FileOutputStream out = new FileOutputStream(filename, append); - byte buff[] = new byte[rawData.length]; - in.read(buff, 0, buff.length); - out.write(buff, 0, rawData.length); - out.flush(); - out.close(); - - return rawData.length; - } - - /** - * Truncate the file to size - * - * @param filename - * @param size - * @throws FileNotFoundException, IOException - * @throws NoModificationAllowedException - */ - private long truncateFile(String filename, long size) throws FileNotFoundException, IOException, NoModificationAllowedException { - DataResource dataResource = DataResource.initiateNewDataRequestForUri(filename, webView.pluginManager, cordova, "FileUtils.truncateFile"); - if(!dataResource.isWritable()) { - throw new NoModificationAllowedException("Couldn't truncate file as it is not writable"); - } - File file = dataResource.getRealFile(); - if(file == null) { - throw new FileNotFoundException("Couldn't get the file"); - } - - RandomAccessFile raf = new RandomAccessFile(file, "rw"); - try { - if (raf.length() >= size) { - FileChannel channel = raf.getChannel(); - channel.truncate(size); - return size; - } - - return raf.length(); - } finally { - raf.close(); - } - } -} diff --git a/framework/src/org/apache/cordova/GPSListener.java b/framework/src/org/apache/cordova/GPSListener.java deleted file mode 100755 index daaf7eeb..00000000 --- a/framework/src/org/apache/cordova/GPSListener.java +++ /dev/null @@ -1,50 +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 android.location.LocationManager; - -/** - * This class handles requests for GPS location services. - * - */ -public class GPSListener extends CordovaLocationListener { - public GPSListener(LocationManager locationManager, GeoBroker m) { - super(locationManager, m, "[Cordova GPSListener]"); - } - - - /** - * Start requesting location updates. - * - * @param interval - */ - @Override - protected void start() { - if (!this.running) { - if (this.locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) { - this.running = true; - this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 60000, 0, this); - } else { - this.fail(CordovaLocationListener.POSITION_UNAVAILABLE, "GPS provider is not available."); - } - } - } -} diff --git a/framework/src/org/apache/cordova/GeoBroker.java b/framework/src/org/apache/cordova/GeoBroker.java deleted file mode 100644 index 4a07b738..00000000 --- a/framework/src/org/apache/cordova/GeoBroker.java +++ /dev/null @@ -1,206 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.Context; -import android.location.Location; -import android.location.LocationManager; - -/* - * This class is the interface to the Geolocation. It's bound to the geo object. - * - * This class only starts and stops various GeoListeners, which consist of a GPS and a Network Listener - */ - -public class GeoBroker extends CordovaPlugin { - private GPSListener gpsListener; - private NetworkListener networkListener; - private LocationManager locationManager; - - /** - * Constructor. - */ - public GeoBroker() { - } - - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackContext The callback id used when calling back into JavaScript. - * @return True if the action was valid, or false if not. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - if (this.locationManager == null) { - this.locationManager = (LocationManager) this.cordova.getActivity().getSystemService(Context.LOCATION_SERVICE); - this.networkListener = new NetworkListener(this.locationManager, this); - this.gpsListener = new GPSListener(this.locationManager, this); - } - - if ( locationManager.isProviderEnabled( LocationManager.GPS_PROVIDER ) || - locationManager.isProviderEnabled( LocationManager.NETWORK_PROVIDER )) { - - if (action.equals("getLocation")) { - boolean enableHighAccuracy = args.getBoolean(0); - int maximumAge = args.getInt(1); - Location last = this.locationManager.getLastKnownLocation((enableHighAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER)); - // Check if we can use lastKnownLocation to get a quick reading and use less battery - if (last != null && (System.currentTimeMillis() - last.getTime()) <= maximumAge) { - PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last)); - callbackContext.sendPluginResult(result); - } else { - this.getCurrentLocation(callbackContext, enableHighAccuracy, args.optInt(2, 60000)); - } - } - else if (action.equals("addWatch")) { - String id = args.getString(0); - boolean enableHighAccuracy = args.getBoolean(1); - this.addWatch(id, callbackContext, enableHighAccuracy); - } - else if (action.equals("clearWatch")) { - String id = args.getString(0); - this.clearWatch(id); - } - else { - return false; - } - } else { - PluginResult.Status status = PluginResult.Status.NO_RESULT; - String message = "Location API is not available for this device."; - PluginResult result = new PluginResult(status, message); - callbackContext.sendPluginResult(result); - } - return true; - } - - private void clearWatch(String id) { - this.gpsListener.clearWatch(id); - this.networkListener.clearWatch(id); - } - - private void getCurrentLocation(CallbackContext callbackContext, boolean enableHighAccuracy, int timeout) { - if (enableHighAccuracy) { - this.gpsListener.addCallback(callbackContext, timeout); - } else { - this.networkListener.addCallback(callbackContext, timeout); - } - } - - private void addWatch(String timerId, CallbackContext callbackContext, boolean enableHighAccuracy) { - if (enableHighAccuracy) { - this.gpsListener.addWatch(timerId, callbackContext); - } else { - this.networkListener.addWatch(timerId, callbackContext); - } - } - - /** - * Called when the activity is to be shut down. - * Stop listener. - */ - public void onDestroy() { - if (this.networkListener != null) { - this.networkListener.destroy(); - this.networkListener = null; - } - if (this.gpsListener != null) { - this.gpsListener.destroy(); - this.gpsListener = null; - } - } - - /** - * Called when the view navigates. - * Stop the listeners. - */ - public void onReset() { - this.onDestroy(); - } - - public JSONObject returnLocationJSON(Location loc) { - JSONObject o = new JSONObject(); - - try { - 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("velocity", loc.getSpeed()); - o.put("timestamp", loc.getTime()); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - return o; - } - - public void win(Location loc, CallbackContext callbackContext, boolean keepCallback) { - PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(loc)); - result.setKeepCallback(keepCallback); - callbackContext.sendPluginResult(result); - } - - /** - * 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, CallbackContext callbackContext, boolean keepCallback) { - 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); - } - - result.setKeepCallback(keepCallback); - callbackContext.sendPluginResult(result); - } - - public boolean isGlobalListener(CordovaLocationListener listener) - { - if (gpsListener != null && networkListener != null) - { - return gpsListener.equals(listener) || networkListener.equals(listener); - } - else - return false; - } -} diff --git a/framework/src/org/apache/cordova/Globalization.java b/framework/src/org/apache/cordova/Globalization.java deleted file mode 100644 index 5c75e100..00000000 --- a/framework/src/org/apache/cordova/Globalization.java +++ /dev/null @@ -1,577 +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.text.DateFormat; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Comparator; -import java.util.Currency; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; - -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.annotation.TargetApi; -import android.text.format.Time; - -/** - * - */ -public class Globalization extends CordovaPlugin { - //GlobalizationCommand Plugin Actions - public static final String GETLOCALENAME = "getLocaleName"; - public static final String DATETOSTRING = "dateToString"; - public static final String STRINGTODATE = "stringToDate"; - public static final String GETDATEPATTERN = "getDatePattern"; - public static final String GETDATENAMES = "getDateNames"; - public static final String ISDAYLIGHTSAVINGSTIME = "isDayLightSavingsTime"; - public static final String GETFIRSTDAYOFWEEK = "getFirstDayOfWeek"; - public static final String NUMBERTOSTRING = "numberToString"; - public static final String STRINGTONUMBER = "stringToNumber"; - public static final String GETNUMBERPATTERN = "getNumberPattern"; - public static final String GETCURRENCYPATTERN = "getCurrencyPattern"; - public static final String GETPREFERREDLANGUAGE = "getPreferredLanguage"; - - //GlobalizationCommand Option Parameters - public static final String OPTIONS = "options"; - public static final String FORMATLENGTH = "formatLength"; - //public static final String SHORT = "short"; //default for dateToString format - public static final String MEDIUM = "medium"; - public static final String LONG = "long"; - public static final String FULL = "full"; - public static final String SELECTOR = "selector"; - //public static final String DATEANDTIME = "date and time"; //default for dateToString - public static final String DATE = "date"; - public static final String TIME = "time"; - public static final String DATESTRING = "dateString"; - public static final String TYPE = "type"; - public static final String ITEM = "item"; - public static final String NARROW = "narrow"; - public static final String WIDE = "wide"; - public static final String MONTHS = "months"; - public static final String DAYS = "days"; - //public static final String DECMIAL = "wide"; //default for numberToString - public static final String NUMBER = "number"; - public static final String NUMBERSTRING = "numberString"; - public static final String PERCENT = "percent"; - public static final String CURRENCY = "currency"; - public static final String CURRENCYCODE = "currencyCode"; - - @Override - public boolean execute(String action, JSONArray data, CallbackContext callbackContext) { - JSONObject obj = new JSONObject(); - - try{ - if (action.equals(GETLOCALENAME)){ - obj = getLocaleName(); - }else if (action.equals(GETPREFERREDLANGUAGE)){ - obj = getPreferredLanguage(); - } else if (action.equalsIgnoreCase(DATETOSTRING)) { - obj = getDateToString(data); - }else if(action.equalsIgnoreCase(STRINGTODATE)){ - obj = getStringtoDate(data); - }else if(action.equalsIgnoreCase(GETDATEPATTERN)){ - obj = getDatePattern(data); - }else if(action.equalsIgnoreCase(GETDATENAMES)){ - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.GINGERBREAD) { - throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); - } else { - obj = getDateNames(data); - } - }else if(action.equalsIgnoreCase(ISDAYLIGHTSAVINGSTIME)){ - obj = getIsDayLightSavingsTime(data); - }else if(action.equalsIgnoreCase(GETFIRSTDAYOFWEEK)){ - obj = getFirstDayOfWeek(data); - }else if(action.equalsIgnoreCase(NUMBERTOSTRING)){ - obj = getNumberToString(data); - }else if(action.equalsIgnoreCase(STRINGTONUMBER)){ - obj = getStringToNumber(data); - }else if(action.equalsIgnoreCase(GETNUMBERPATTERN)){ - obj = getNumberPattern(data); - }else if(action.equalsIgnoreCase(GETCURRENCYPATTERN)){ - obj = getCurrencyPattern(data); - }else { - return false; - } - - callbackContext.success(obj); - }catch (GlobalizationError ge){ - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ge.toJson())); - }catch (Exception e){ - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); - } - return true; - } - /* - * @Description: Returns the string identifier for the client's current locale setting - * - * @Return: JSONObject - * Object.value {String}: The locale identifier - * - * @throws: GlobalizationError.UNKNOWN_ERROR - */ - private JSONObject getLocaleName() throws GlobalizationError{ - JSONObject obj = new JSONObject(); - try{ - obj.put("value",Locale.getDefault().toString());//get the locale from the Android Device - return obj; - }catch(Exception e){ - throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); - } - } - /* - * @Description: Returns the string identifier for the client's current language - * - * @Return: JSONObject - * Object.value {String}: The language identifier - * - * @throws: GlobalizationError.UNKNOWN_ERROR - */ - private JSONObject getPreferredLanguage() throws GlobalizationError { - JSONObject obj = new JSONObject(); - try { - obj.put("value", Locale.getDefault().getDisplayLanguage().toString()); - return obj; - } catch (Exception e) { - throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); - } - } - /* - * @Description: Returns a date formatted as a string according to the client's user preferences and - * calendar using the time zone of the client. - * - * @Return: JSONObject - * Object.value {String}: The localized date string - * - * @throws: GlobalizationError.FORMATTING_ERROR - */ - private JSONObject getDateToString(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - try{ - Date date = new Date((Long)options.getJSONObject(0).get(DATE)); - - //get formatting pattern from android device (Will only have device specific formatting for short form of date) or options supplied - JSONObject datePattern = getDatePattern(options); - SimpleDateFormat fmt = new SimpleDateFormat(datePattern.getString("pattern")); - - //return formatted date - return obj.put("value",fmt.format(date)); - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR); - } - } - - /* - * @Description: Parses a date formatted as a string according to the client's user - * preferences and calendar using the time zone of the client and returns - * the corresponding date object - * @Return: JSONObject - * Object.year {Number}: The four digit year - * Object.month {Number}: The month from (0 - 11) - * Object.day {Number}: The day from (1 - 31) - * Object.hour {Number}: The hour from (0 - 23) - * Object.minute {Number}: The minute from (0 - 59) - * Object.second {Number}: The second from (0 - 59) - * Object.millisecond {Number}: The milliseconds (from 0 - 999), not available on all platforms - * - * @throws: GlobalizationError.PARSING_ERROR - */ - private JSONObject getStringtoDate(JSONArray options)throws GlobalizationError{ - JSONObject obj = new JSONObject(); - Date date; - try{ - //get format pattern from android device (Will only have device specific formatting for short form of date) or options supplied - DateFormat fmt = new SimpleDateFormat(getDatePattern(options).getString("pattern")); - - //attempt parsing string based on user preferences - date = fmt.parse(options.getJSONObject(0).get(DATESTRING).toString()); - - //set Android Time object - Time time = new Time(); - time.set(date.getTime()); - - //return properties; - obj.put("year", time.year); - obj.put("month", time.month); - obj.put("day", time.monthDay); - obj.put("hour", time.hour); - obj.put("minute", time.minute); - obj.put("second", time.second); - obj.put("millisecond", new Long(0)); - return obj; - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.PARSING_ERROR); - } - } - - /* - * @Description: Returns a pattern string for formatting and parsing dates according to the client's - * user preferences. - * @Return: JSONObject - * - * Object.pattern {String}: The date and time pattern for formatting and parsing dates. - * The patterns follow Unicode Technical Standard #35 - * http://unicode.org/reports/tr35/tr35-4.html - * Object.timezone {String}: The abbreviated name of the time zone on the client - * Object.utc_offset {Number}: The current difference in seconds between the client's - * time zone and coordinated universal time. - * Object.dst_offset {Number}: The current daylight saving time offset in seconds - * between the client's non-daylight saving's time zone - * and the client's daylight saving's time zone. - * - * @throws: GlobalizationError.PATTERN_ERROR - */ - private JSONObject getDatePattern(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - - try{ - SimpleDateFormat fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getDateFormat(this.cordova.getActivity()); //default user preference for date - SimpleDateFormat fmtTime = (SimpleDateFormat)android.text.format.DateFormat.getTimeFormat(this.cordova.getActivity()); //default user preference for time - - String fmt = fmtDate.toLocalizedPattern() + " " + fmtTime.toLocalizedPattern(); //default SHORT date/time format. ex. dd/MM/yyyy h:mm a - - //get Date value + options (if available) - if (options.getJSONObject(0).length() > 1){ - //options were included - - //get formatLength option - if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(FORMATLENGTH)){ - String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(FORMATLENGTH); - if (fmtOpt.equalsIgnoreCase(MEDIUM)){//medium - fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getMediumDateFormat(this.cordova.getActivity()); - }else if (fmtOpt.equalsIgnoreCase(LONG) || fmtOpt.equalsIgnoreCase(FULL)){ //long/full - fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getLongDateFormat(this.cordova.getActivity()); - } - } - - //return pattern type - fmt = fmtDate.toLocalizedPattern() + " " + fmtTime.toLocalizedPattern(); - if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(SELECTOR)){ - String selOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(SELECTOR); - if (selOpt.equalsIgnoreCase(DATE)){ - fmt = fmtDate.toLocalizedPattern(); - }else if (selOpt.equalsIgnoreCase(TIME)){ - fmt = fmtTime.toLocalizedPattern(); - } - } - } - - //TimeZone from users device - //TimeZone tz = Calendar.getInstance(Locale.getDefault()).getTimeZone(); //substitute method - TimeZone tz = TimeZone.getTimeZone(Time.getCurrentTimezone()); - - obj.put("pattern", fmt); - obj.put("timezone", tz.getDisplayName(tz.inDaylightTime(Calendar.getInstance().getTime()),TimeZone.SHORT)); - obj.put("utc_offset", tz.getRawOffset()/1000); - obj.put("dst_offset", tz.getDSTSavings()/1000); - return obj; - - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.PATTERN_ERROR); - } - } - - /* - * @Description: Returns an array of either the names of the months or days of the week - * according to the client's user preferences and calendar - * @Return: JSONObject - * Object.value {Array{String}}: The array of names starting from either - * the first month in the year or the - * first day of the week. - * - * @throws: GlobalizationError.UNKNOWN_ERROR - */ - @TargetApi(9) - private JSONObject getDateNames(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - //String[] value; - JSONArray value = new JSONArray(); - List namesList = new ArrayList(); - final Map namesMap; // final needed for sorting with anonymous comparator - try{ - int type = 0; //default wide - int item = 0; //default months - - //get options if available - if (options.getJSONObject(0).length() > 0){ - //get type if available - if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){ - String t = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE); - if (t.equalsIgnoreCase(NARROW)){type++;} //DateUtils.LENGTH_MEDIUM - } - //get item if available - if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(ITEM)){ - String t = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(ITEM); - if (t.equalsIgnoreCase(DAYS)){item += 10;} //Days of week start at 1 - } - } - //determine return value - int method = item + type; - if (method == 1) { //months and narrow - namesMap = Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.SHORT, Locale.getDefault()); - } else if (method == 10) { //days and wide - namesMap = Calendar.getInstance().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault()); - } else if (method == 11) { //days and narrow - namesMap = Calendar.getInstance().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.getDefault()); - } else { //default: months and wide - namesMap = Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.LONG, Locale.getDefault()); - } - - // save names as a list - for(String name : namesMap.keySet()) { - namesList.add(name); - } - - // sort the list according to values in namesMap - Collections.sort(namesList, new Comparator() { - public int compare(String arg0, String arg1) { - return namesMap.get(arg0).compareTo(namesMap.get(arg1)); - } - }); - - // convert nameList into JSONArray of String objects - for (int i = 0; i < namesList.size(); i ++){ - value.put(namesList.get(i)); - } - - //return array of names - return obj.put("value", value); - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); - } - } - - /* - * @Description: Returns whether daylight savings time is in effect for a given date using the client's - * time zone and calendar. - * @Return: JSONObject - * Object.dst {Boolean}: The value "true" indicates that daylight savings time is - * in effect for the given date and "false" indicate that it is not. * - * - * @throws: GlobalizationError.UNKNOWN_ERROR - */ - private JSONObject getIsDayLightSavingsTime(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - boolean dst = false; - try{ - Date date = new Date((Long)options.getJSONObject(0).get(DATE)); - //TimeZone tz = Calendar.getInstance(Locale.getDefault()).getTimeZone(); - TimeZone tz = TimeZone.getTimeZone(Time.getCurrentTimezone()); - dst = tz.inDaylightTime(date); //get daylight savings data from date object and user timezone settings - - return obj.put("dst",dst); - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); - } - } - - /* - * @Description: Returns the first day of the week according to the client's user preferences and calendar. - * The days of the week are numbered starting from 1 where 1 is considered to be Sunday. - * @Return: JSONObject - * Object.value {Number}: The number of the first day of the week. - * - * @throws: GlobalizationError.UNKNOWN_ERROR - */ - private JSONObject getFirstDayOfWeek(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - try{ - int value = Calendar.getInstance(Locale.getDefault()).getFirstDayOfWeek(); //get first day of week based on user locale settings - return obj.put("value", value); - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); - } - } - - /* - * @Description: Returns a number formatted as a string according to the client's user preferences. - * @Return: JSONObject - * Object.value {String}: The formatted number string. - * - * @throws: GlobalizationError.FORMATTING_ERROR - */ - private JSONObject getNumberToString(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - String value = ""; - try{ - DecimalFormat fmt = getNumberFormatInstance(options);//returns Decimal/Currency/Percent instance - value = fmt.format(options.getJSONObject(0).get(NUMBER)); - return obj.put("value", value); - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR); - } - } - - /* - * @Description: Parses a number formatted as a string according to the client's user preferences and - * returns the corresponding number. - * @Return: JSONObject - * Object.value {Number}: The parsed number. - * - * @throws: GlobalizationError.PARSING_ERROR - */ - private JSONObject getStringToNumber(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - Number value; - try{ - DecimalFormat fmt = getNumberFormatInstance(options); //returns Decimal/Currency/Percent instance - value = fmt.parse((String)options.getJSONObject(0).get(NUMBERSTRING)); - return obj.put("value", value); - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.PARSING_ERROR); - } - } - - /* - * @Description: Returns a pattern string for formatting and parsing numbers according to the client's user - * preferences. - * @Return: JSONObject - * Object.pattern {String}: The number pattern for formatting and parsing numbers. - * The patterns follow Unicode Technical Standard #35. - * http://unicode.org/reports/tr35/tr35-4.html - * Object.symbol {String}: The symbol to be used when formatting and parsing - * e.g., percent or currency symbol. - * Object.fraction {Number}: The number of fractional digits to use when parsing and - * formatting numbers. - * Object.rounding {Number}: The rounding increment to use when parsing and formatting. - * Object.positive {String}: The symbol to use for positive numbers when parsing and formatting. - * Object.negative: {String}: The symbol to use for negative numbers when parsing and formatting. - * Object.decimal: {String}: The decimal symbol to use for parsing and formatting. - * Object.grouping: {String}: The grouping symbol to use for parsing and formatting. - * - * @throws: GlobalizationError.PATTERN_ERROR - */ - private JSONObject getNumberPattern(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - try{ - //uses java.text.DecimalFormat to format value - DecimalFormat fmt = (DecimalFormat) DecimalFormat.getInstance(Locale.getDefault()); //default format - String symbol = String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator()); - //get Date value + options (if available) - if (options.getJSONObject(0).length() > 0){ - //options were included - if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){ - String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE); - if (fmtOpt.equalsIgnoreCase(CURRENCY)){ - fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault()); - symbol = fmt.getDecimalFormatSymbols().getCurrencySymbol(); - }else if(fmtOpt.equalsIgnoreCase(PERCENT)){ - fmt = (DecimalFormat) DecimalFormat.getPercentInstance(Locale.getDefault()); - symbol = String.valueOf(fmt.getDecimalFormatSymbols().getPercent()); - } - } - } - - //return properties - obj.put("pattern", fmt.toPattern()); - obj.put("symbol", symbol); - obj.put("fraction", fmt.getMinimumFractionDigits()); - obj.put("rounding", new Integer(0)); - obj.put("positive", fmt.getPositivePrefix()); - obj.put("negative", fmt.getNegativePrefix()); - obj.put("decimal", String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator())); - obj.put("grouping", String.valueOf(fmt.getDecimalFormatSymbols().getGroupingSeparator())); - - return obj; - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.PATTERN_ERROR); - } - } - - /* - * @Description: Returns a pattern string for formatting and parsing currency values according to the client's - * user preferences and ISO 4217 currency code. - * @Return: JSONObject - * Object.pattern {String}: The currency pattern for formatting and parsing currency values. - * The patterns follow Unicode Technical Standard #35 - * http://unicode.org/reports/tr35/tr35-4.html - * Object.code {String}: The ISO 4217 currency code for the pattern. - * Object.fraction {Number}: The number of fractional digits to use when parsing and - * formatting currency. - * Object.rounding {Number}: The rounding increment to use when parsing and formatting. - * Object.decimal: {String}: The decimal symbol to use for parsing and formatting. - * Object.grouping: {String}: The grouping symbol to use for parsing and formatting. - * - * @throws: GlobalizationError.FORMATTING_ERROR - */ - private JSONObject getCurrencyPattern(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - try{ - //get ISO 4217 currency code - String code = options.getJSONObject(0).getString(CURRENCYCODE); - - //uses java.text.DecimalFormat to format value - DecimalFormat fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault()); - - //set currency format - Currency currency = Currency.getInstance(code); - fmt.setCurrency(currency); - - //return properties - obj.put("pattern", fmt.toPattern()); - obj.put("code", currency.getCurrencyCode()); - obj.put("fraction", fmt.getMinimumFractionDigits()); - obj.put("rounding", new Integer(0)); - obj.put("decimal", String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator())); - obj.put("grouping", String.valueOf(fmt.getDecimalFormatSymbols().getGroupingSeparator())); - - return obj; - }catch(Exception ge){ - throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR); - } - } - - /* - * @Description: Parses a JSONArray from user options and returns the correct Instance of Decimal/Percent/Currency. - * @Return: DecimalFormat : The Instance to use. - * - * @throws: JSONException - */ - private DecimalFormat getNumberFormatInstance(JSONArray options) throws JSONException{ - DecimalFormat fmt = (DecimalFormat)DecimalFormat.getInstance(Locale.getDefault()); //default format - try{ - if (options.getJSONObject(0).length() > 1){ - //options were included - if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){ - String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE); - if (fmtOpt.equalsIgnoreCase(CURRENCY)){ - fmt = (DecimalFormat)DecimalFormat.getCurrencyInstance(Locale.getDefault()); - }else if(fmtOpt.equalsIgnoreCase(PERCENT)){ - fmt = (DecimalFormat)DecimalFormat.getPercentInstance(Locale.getDefault()); - } - } - } - - }catch (JSONException je){} - return fmt; - } -} diff --git a/framework/src/org/apache/cordova/GlobalizationError.java b/framework/src/org/apache/cordova/GlobalizationError.java deleted file mode 100644 index 8a171d42..00000000 --- a/framework/src/org/apache/cordova/GlobalizationError.java +++ /dev/null @@ -1,108 +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 org.json.JSONException; -import org.json.JSONObject; - -/** - * @description Exception class representing defined Globalization error codes - * @Globalization error codes: - * GlobalizationError.UNKNOWN_ERROR = 0; - * GlobalizationError.FORMATTING_ERROR = 1; - * GlobalizationError.PARSING_ERROR = 2; - * GlobalizationError.PATTERN_ERROR = 3; - */ -public class GlobalizationError extends Exception{ - /** - * - */ - private static final long serialVersionUID = 1L; - public static final String UNKNOWN_ERROR = "UNKNOWN_ERROR"; - public static final String FORMATTING_ERROR = "FORMATTING_ERROR"; - public static final String PARSING_ERROR = "PARSING_ERROR"; - public static final String PATTERN_ERROR = "PATTERN_ERROR"; - - int error = 0; //default unknown error thrown - /** - * Default constructor - */ - public GlobalizationError() {} - /** - * Create an exception returning an error code - * - * @param s - */ - public GlobalizationError(String s) { - if (s.equalsIgnoreCase(FORMATTING_ERROR)){ - error = 1; - }else if (s.equalsIgnoreCase(PARSING_ERROR)){ - error = 2; - }else if (s.equalsIgnoreCase(PATTERN_ERROR)){ - error = 3; - } - } - /** - * get error string based on error code - * - * @param String msg - */ - public String getErrorString(){ - String msg = ""; - switch (error){ - case 0: - msg = UNKNOWN_ERROR; - break; - case 1: - msg = FORMATTING_ERROR; - break; - case 2: - msg = PARSING_ERROR; - break; - case 3: - msg = PATTERN_ERROR; - break; - } - return msg; - } - /** - * get error code - * - * @param String msg - */ - public int getErrorCode(){ - return error; - } - - /** - * get the json version of this object to return to javascript - * @return - */ - public JSONObject toJson() { - JSONObject obj = new JSONObject(); - try { - obj.put("code", getErrorCode()); - obj.put("message", getErrorString()); - } catch (JSONException e) { - // never happens - } - return obj; - } -} diff --git a/framework/src/org/apache/cordova/HttpHandler.java b/framework/src/org/apache/cordova/HttpHandler.java deleted file mode 100755 index 1de8302e..00000000 --- a/framework/src/org/apache/cordova/HttpHandler.java +++ /dev/null @@ -1,86 +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.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; - -public class HttpHandler { - - protected Boolean get(String url, String file) - { - HttpEntity entity = getHttpEntity(url); - try { - writeToDisk(entity, file); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - try { - entity.consumeContent(); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - return true; - } - - private HttpEntity getHttpEntity(String url) - /** - * get the http entity at a given url - */ - { - HttpEntity entity = null; - try { - DefaultHttpClient httpclient = new DefaultHttpClient(); - HttpGet httpget = new HttpGet(url); - HttpResponse response = httpclient.execute(httpget); - entity = response.getEntity(); - } catch (Exception e) { e.printStackTrace(); return null; } - return entity; - } - - private void writeToDisk(HttpEntity entity, String file) throws IllegalStateException, IOException - /** - * writes a HTTP entity to the specified filename and location on disk - */ - { - //int i = 0; - String FilePath = "/sdcard/" + file; - InputStream in = entity.getContent(); - byte buff[] = new byte[1024]; - FileOutputStream out = - new FileOutputStream(FilePath); - do { - int numread = in.read(buff); - if (numread <= 0) - break; - out.write(buff, 0, numread); - //i++; - } while (true); - out.flush(); - out.close(); - } -} diff --git a/framework/src/org/apache/cordova/InAppBrowser.java b/framework/src/org/apache/cordova/InAppBrowser.java deleted file mode 100644 index 8e78baa2..00000000 --- a/framework/src/org/apache/cordova/InAppBrowser.java +++ /dev/null @@ -1,813 +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.HashMap; -import java.util.StringTokenizer; - -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.LOG; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.text.InputType; -import android.util.Log; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.webkit.WebChromeClient; -import android.webkit.GeolocationPermissions.Callback; -import android.webkit.JsPromptResult; -import android.webkit.WebSettings; -import android.webkit.WebStorage; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; - -@SuppressLint("SetJavaScriptEnabled") -public class InAppBrowser extends CordovaPlugin { - - private static final String NULL = "null"; - protected static final String LOG_TAG = "InAppBrowser"; - private static final String SELF = "_self"; - private static final String SYSTEM = "_system"; - // private static final String BLANK = "_blank"; - private static final String LOCATION = "location"; - private static final String EXIT_EVENT = "exit"; - private static final String LOAD_START_EVENT = "loadstart"; - private static final String LOAD_STOP_EVENT = "loadstop"; - private static final String LOAD_ERROR_EVENT = "loaderror"; - private static final String CLOSE_BUTTON_CAPTION = "closebuttoncaption"; - private long MAX_QUOTA = 100 * 1024 * 1024; - - private Dialog dialog; - private WebView inAppWebView; - private EditText edittext; - private boolean showLocationBar = true; - private CallbackContext callbackContext; - private String buttonLabel = "Done"; - - /** - * 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 boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - try { - if (action.equals("open")) { - this.callbackContext = callbackContext; - String url = args.getString(0); - String target = args.optString(1); - if (target == null || target.equals("") || target.equals(NULL)) { - target = SELF; - } - HashMap features = parseFeature(args.optString(2)); - - Log.d(LOG_TAG, "target = " + target); - - url = updateUrl(url); - String result = ""; - - // SELF - if (SELF.equals(target)) { - Log.d(LOG_TAG, "in self"); - // load in webview - if (url.startsWith("file://") || url.startsWith("javascript:") - || Config.isUrlWhiteListed(url)) { - this.webView.loadUrl(url); - } - //Load the dialer - else if (url.startsWith(WebView.SCHEME_TEL)) - { - try { - Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(url)); - this.cordova.getActivity().startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); - } - } - // load in InAppBrowser - else { - result = this.showWebPage(url, features); - } - } - // SYSTEM - else if (SYSTEM.equals(target)) { - Log.d(LOG_TAG, "in system"); - result = this.openExternal(url); - } - // BLANK - or anything else - else { - Log.d(LOG_TAG, "in blank"); - result = this.showWebPage(url, features); - } - - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); - pluginResult.setKeepCallback(true); - this.callbackContext.sendPluginResult(pluginResult); - } - else if (action.equals("close")) { - closeDialog(); - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); - } - else if (action.equals("injectScriptCode")) { - String jsWrapper = null; - if (args.getBoolean(1)) { - jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId()); - } - injectDeferredObject(args.getString(0), jsWrapper); - } - else if (action.equals("injectScriptFile")) { - String jsWrapper; - if (args.getBoolean(1)) { - jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId()); - } else { - jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)"; - } - injectDeferredObject(args.getString(0), jsWrapper); - } - else if (action.equals("injectStyleCode")) { - String jsWrapper; - if (args.getBoolean(1)) { - jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); - } else { - jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)"; - } - injectDeferredObject(args.getString(0), jsWrapper); - } - else if (action.equals("injectStyleFile")) { - String jsWrapper; - if (args.getBoolean(1)) { - jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); - } else { - jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)"; - } - injectDeferredObject(args.getString(0), jsWrapper); - } - else { - return false; - } - } catch (JSONException e) { - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); - } - return true; - } - - /** - * Inject an object (script or style) into the InAppBrowser WebView. - * - * This is a helper method for the inject{Script|Style}{Code|File} API calls, which - * provides a consistent method for injecting JavaScript code into the document. - * - * If a wrapper string is supplied, then the source string will be JSON-encoded (adding - * quotes) and wrapped using string formatting. (The wrapper string should have a single - * '%s' marker) - * - * @param source The source object (filename or script/style text) to inject into - * the document. - * @param jsWrapper A JavaScript string to wrap the source string in, so that the object - * is properly injected, or null if the source string is JavaScript text - * which should be executed directly. - */ - private void injectDeferredObject(String source, String jsWrapper) { - String scriptToInject; - if (jsWrapper != null) { - org.json.JSONArray jsonEsc = new org.json.JSONArray(); - jsonEsc.put(source); - String jsonRepr = jsonEsc.toString(); - String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1); - scriptToInject = String.format(jsWrapper, jsonSourceString); - } else { - scriptToInject = source; - } - // This action will have the side-effect of blurring the currently focused element - this.inAppWebView.loadUrl("javascript:" + scriptToInject); - } - - /** - * Put the list of features into a hash map - * - * @param optString - * @return - */ - private HashMap parseFeature(String optString) { - if (optString.equals(NULL)) { - return null; - } else { - HashMap map = new HashMap(); - StringTokenizer features = new StringTokenizer(optString, ","); - StringTokenizer option; - while(features.hasMoreElements()) { - option = new StringTokenizer(features.nextToken(), "="); - if (option.hasMoreElements()) { - String key = option.nextToken(); - if (key.equalsIgnoreCase(CLOSE_BUTTON_CAPTION)) { - this.buttonLabel = option.nextToken(); - } else { - Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE; - map.put(key, value); - } - } - } - return map; - } - } - - /** - * Convert relative URL to full path - * - * @param url - * @return - */ - private String updateUrl(String url) { - Uri newUrl = Uri.parse(url); - if (newUrl.isRelative()) { - url = this.webView.getUrl().substring(0, this.webView.getUrl().lastIndexOf("/")+1) + url; - } - return url; - } - - /** - * Display a new browser with the specified URL. - * - * @param url The url to load. - * @param usePhoneGap Load url in PhoneGap webview - * @return "" if ok, or error message. - */ - public String openExternal(String url) { - try { - Intent intent = null; - intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - this.cordova.getActivity().startActivity(intent); - return ""; - } catch (android.content.ActivityNotFoundException e) { - Log.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString()); - return e.toString(); - } - } - - /** - * Closes the dialog - */ - private void closeDialog() { - try { - this.inAppWebView.loadUrl("about:blank"); - JSONObject obj = new JSONObject(); - obj.put("type", EXIT_EVENT); - - sendUpdate(obj, false); - } catch (JSONException ex) { - Log.d(LOG_TAG, "Should never happen"); - } - - if (dialog != null) { - dialog.dismiss(); - } - } - - /** - * Checks to see if it is possible to go back one page in history, then does so. - */ - private void goBack() { - if (this.inAppWebView.canGoBack()) { - this.inAppWebView.goBack(); - } - } - - /** - * Checks to see if it is possible to go forward one page in history, then does so. - */ - private void goForward() { - if (this.inAppWebView.canGoForward()) { - this.inAppWebView.goForward(); - } - } - - /** - * Navigate to the new page - * - * @param url to load - */ - private void navigate(String url) { - InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0); - - if (!url.startsWith("http") && !url.startsWith("file:")) { - this.inAppWebView.loadUrl("http://" + url); - } else { - this.inAppWebView.loadUrl(url); - } - this.inAppWebView.requestFocus(); - } - - - /** - * Should we show the location bar? - * - * @return boolean - */ - private boolean getShowLocationBar() { - return this.showLocationBar; - } - - /** - * Display a new browser with the specified URL. - * - * @param url The url to load. - * @param jsonObject - */ - public String showWebPage(final String url, HashMap features) { - // Determine if we should hide the location bar. - showLocationBar = true; - if (features != null) { - Boolean show = features.get(LOCATION); - if (show != null) { - showLocationBar = show.booleanValue(); - } - } - - final CordovaWebView thatWebView = this.webView; - - // Create dialog in new thread - Runnable runnable = new Runnable() { - /** - * Convert our DIP units to Pixels - * - * @return int - */ - private int dpToPixels(int dipValue) { - int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, - (float) dipValue, - cordova.getActivity().getResources().getDisplayMetrics() - ); - - return value; - } - - public void run() { - // Let's create the main dialog - dialog = new Dialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar); - dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog; - dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - dialog.setCancelable(true); - dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { - public void onDismiss(DialogInterface dialog) { - try { - JSONObject obj = new JSONObject(); - obj.put("type", EXIT_EVENT); - - sendUpdate(obj, false); - } catch (JSONException e) { - Log.d(LOG_TAG, "Should never happen"); - } - } - }); - - // Main container layout - LinearLayout main = new LinearLayout(cordova.getActivity()); - main.setOrientation(LinearLayout.VERTICAL); - - // Toolbar layout - RelativeLayout toolbar = new RelativeLayout(cordova.getActivity()); - toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44))); - toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2)); - toolbar.setHorizontalGravity(Gravity.LEFT); - toolbar.setVerticalGravity(Gravity.TOP); - - // Action Button Container layout - RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity()); - actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); - actionButtonContainer.setHorizontalGravity(Gravity.LEFT); - actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL); - actionButtonContainer.setId(1); - - // Back button - Button back = new Button(cordova.getActivity()); - RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT); - back.setLayoutParams(backLayoutParams); - back.setContentDescription("Back Button"); - back.setId(2); - back.setText("<"); - back.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - goBack(); - } - }); - - // Forward button - Button forward = new Button(cordova.getActivity()); - RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2); - forward.setLayoutParams(forwardLayoutParams); - forward.setContentDescription("Forward Button"); - forward.setId(3); - forward.setText(">"); - forward.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - goForward(); - } - }); - - // Edit Text Box - edittext = new EditText(cordova.getActivity()); - RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1); - textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5); - edittext.setLayoutParams(textLayoutParams); - edittext.setId(4); - edittext.setSingleLine(true); - edittext.setText(url); - edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI); - edittext.setImeOptions(EditorInfo.IME_ACTION_GO); - edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE - edittext.setOnKeyListener(new View.OnKeyListener() { - public boolean onKey(View v, int keyCode, KeyEvent event) { - // If the event is a key-down event on the "enter" button - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - navigate(edittext.getText().toString()); - return true; - } - return false; - } - }); - - // Close button - Button close = new Button(cordova.getActivity()); - RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); - close.setLayoutParams(closeLayoutParams); - forward.setContentDescription("Close Button"); - close.setId(5); - close.setText(buttonLabel); - close.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - closeDialog(); - } - }); - - // WebView - inAppWebView = new WebView(cordova.getActivity()); - inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView)); - WebViewClient client = new InAppBrowserClient(thatWebView, edittext); - inAppWebView.setWebViewClient(client); - WebSettings settings = inAppWebView.getSettings(); - settings.setJavaScriptEnabled(true); - settings.setJavaScriptCanOpenWindowsAutomatically(true); - settings.setBuiltInZoomControls(true); - /** - * We need to be careful of this line as a future Android release may deprecate it out of existence. - * Can't replace it with the API 8 level call right now as our minimum SDK is 7 until May 2013 - */ - // @TODO: replace with settings.setPluginState(android.webkit.WebSettings.PluginState.ON) - settings.setPluginsEnabled(true); - - //Toggle whether this is enabled or not! - Bundle appSettings = cordova.getActivity().getIntent().getExtras(); - boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true); - if(enableDatabase) - { - String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath(); - settings.setDatabasePath(databasePath); - settings.setDatabaseEnabled(true); - } - settings.setDomStorageEnabled(true); - - inAppWebView.loadUrl(url); - inAppWebView.setId(6); - inAppWebView.getSettings().setLoadWithOverviewMode(true); - inAppWebView.getSettings().setUseWideViewPort(true); - inAppWebView.requestFocus(); - inAppWebView.requestFocusFromTouch(); - - // Add the back and forward buttons to our action button container layout - actionButtonContainer.addView(back); - actionButtonContainer.addView(forward); - - // Add the views to our toolbar - toolbar.addView(actionButtonContainer); - toolbar.addView(edittext); - toolbar.addView(close); - - // Don't add the toolbar if its been disabled - if (getShowLocationBar()) { - // Add our toolbar to our main view/layout - main.addView(toolbar); - } - - // Add our webview to our main view/layout - main.addView(inAppWebView); - - WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); - lp.copyFrom(dialog.getWindow().getAttributes()); - lp.width = WindowManager.LayoutParams.MATCH_PARENT; - lp.height = WindowManager.LayoutParams.MATCH_PARENT; - - dialog.setContentView(main); - dialog.show(); - dialog.getWindow().setAttributes(lp); - } - }; - this.cordova.getActivity().runOnUiThread(runnable); - return ""; - } - - /** - * Create a new plugin success result and send it back to JavaScript - * - * @param obj a JSONObject contain event payload information - */ - private void sendUpdate(JSONObject obj, boolean keepCallback) { - sendUpdate(obj, keepCallback, PluginResult.Status.OK); - } - - /** - * Create a new plugin result and send it back to JavaScript - * - * @param obj a JSONObject contain event payload information - * @param status the status code to return to the JavaScript environment - */ private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) { - PluginResult result = new PluginResult(status, obj); - result.setKeepCallback(keepCallback); - this.callbackContext.sendPluginResult(result); - } - - public class InAppChromeClient extends WebChromeClient { - - private CordovaWebView webView; - - public InAppChromeClient(CordovaWebView webView) { - super(); - this.webView = webView; - } - /** - * Handle database quota exceeded notification. - * - * @param url - * @param databaseIdentifier - * @param currentQuota - * @param estimatedSize - * @param totalUsedQuota - * @param quotaUpdater - */ - @Override - public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, - long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) - { - LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota); - - if (estimatedSize < MAX_QUOTA) - { - //increase for 1Mb - long newQuota = estimatedSize; - LOG.d(LOG_TAG, "calling quotaUpdater.updateQuota newQuota: %d", newQuota); - quotaUpdater.updateQuota(newQuota); - } - else - { - // Set the quota to whatever it is and force an error - // TODO: get docs on how to handle this properly - quotaUpdater.updateQuota(currentQuota); - } - } - - /** - * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. - * - * @param origin - * @param callback - */ - @Override - public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { - super.onGeolocationPermissionsShowPrompt(origin, callback); - callback.invoke(origin, true, false); - } - - /** - * Tell the client to display a prompt dialog to the user. - * If the client returns true, WebView will assume that the client will - * handle the prompt dialog and call the appropriate JsPromptResult method. - * - * The prompt bridge provided for the InAppBrowser is capable of executing any - * oustanding callback belonging to the InAppBrowser plugin. Care has been - * taken that other callbacks cannot be triggered, and that no other code - * execution is possible. - * - * To trigger the bridge, the prompt default value should be of the form: - * - * gap-iab:// - * - * where is the string id of the callback to trigger (something - * like "InAppBrowser0123456789") - * - * If present, the prompt message is expected to be a JSON-encoded value to - * pass to the callback. A JSON_EXCEPTION is returned if the JSON is invalid. - * - * @param view - * @param url - * @param message - * @param defaultValue - * @param result - */ - @Override - public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { - // See if the prompt string uses the 'gap-iab' protocol. If so, the remainder should be the id of a callback to execute. - if (defaultValue != null && defaultValue.startsWith("gap-iab://")) { - PluginResult scriptResult; - String scriptCallbackId = defaultValue.substring(10); - if (scriptCallbackId.startsWith("InAppBrowser")) { - if(message == null || message.length() == 0) { - scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray()); - } else { - try { - scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray(message)); - } catch(JSONException e) { - scriptResult = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage()); - } - } - this.webView.sendPluginResult(scriptResult, scriptCallbackId); - result.confirm(""); - return true; - } - } - return false; - } - - } - - /** - * The webview client receives notifications about appView - */ - public class InAppBrowserClient extends WebViewClient { - EditText edittext; - CordovaWebView webView; - - /** - * Constructor. - * - * @param mContext - * @param edittext - */ - public InAppBrowserClient(CordovaWebView webView, EditText mEditText) { - this.webView = webView; - this.edittext = mEditText; - } - - /** - * Notify the host application that a page has started loading. - * - * @param view The webview initiating the callback. - * @param url The url of the page. - */ - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - super.onPageStarted(view, url, favicon); - String newloc = ""; - if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) { - newloc = url; - } - // If dialing phone (tel:5551212) - else if (url.startsWith(WebView.SCHEME_TEL)) { - try { - Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(url)); - cordova.getActivity().startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); - } - } - - else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - cordova.getActivity().startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString()); - } - } - // If sms:5551212?body=This is the message - else if (url.startsWith("sms:")) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - - // Get address - String address = null; - int parmIndex = url.indexOf('?'); - if (parmIndex == -1) { - address = url.substring(4); - } - else { - address = url.substring(4, parmIndex); - - // If body, then set sms body - Uri uri = Uri.parse(url); - String query = uri.getQuery(); - if (query != null) { - if (query.startsWith("body=")) { - intent.putExtra("sms_body", query.substring(5)); - } - } - } - intent.setData(Uri.parse("sms:" + address)); - intent.putExtra("address", address); - intent.setType("vnd.android-dir/mms-sms"); - cordova.getActivity().startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString()); - } - } - else { - newloc = "http://" + url; - } - - if (!newloc.equals(edittext.getText().toString())) { - edittext.setText(newloc); - } - - try { - JSONObject obj = new JSONObject(); - obj.put("type", LOAD_START_EVENT); - obj.put("url", newloc); - - sendUpdate(obj, true); - } catch (JSONException ex) { - Log.d(LOG_TAG, "Should never happen"); - } - } - - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - - try { - JSONObject obj = new JSONObject(); - obj.put("type", LOAD_STOP_EVENT); - obj.put("url", url); - - sendUpdate(obj, true); - } catch (JSONException ex) { - Log.d(LOG_TAG, "Should never happen"); - } - } - - public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - super.onReceivedError(view, errorCode, description, failingUrl); - - try { - JSONObject obj = new JSONObject(); - obj.put("type", LOAD_ERROR_EVENT); - obj.put("url", failingUrl); - obj.put("code", errorCode); - obj.put("message", description); - - sendUpdate(obj, true, PluginResult.Status.ERROR); - } catch (JSONException ex) { - Log.d(LOG_TAG, "Should never happen"); - } - - } - } -} diff --git a/framework/src/org/apache/cordova/NetworkListener.java b/framework/src/org/apache/cordova/NetworkListener.java deleted file mode 100755 index 7d192595..00000000 --- a/framework/src/org/apache/cordova/NetworkListener.java +++ /dev/null @@ -1,32 +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 android.location.LocationManager; - -/** - * This class handles requests for GPS location services. - * - */ -public class NetworkListener extends CordovaLocationListener { - public NetworkListener(LocationManager locationManager, GeoBroker m) { - super(locationManager, m, "[Cordova NetworkListener]"); - } -} diff --git a/framework/src/org/apache/cordova/NetworkManager.java b/framework/src/org/apache/cordova/NetworkManager.java deleted file mode 100755 index bb4743f2..00000000 --- a/framework/src/org/apache/cordova/NetworkManager.java +++ /dev/null @@ -1,248 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaInterface; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.util.Log; - -public class NetworkManager extends CordovaPlugin { - - public static int NOT_REACHABLE = 0; - public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; - public static int REACHABLE_VIA_WIFI_NETWORK = 2; - - public static final String WIFI = "wifi"; - public static final String WIMAX = "wimax"; - // mobile - public static final String MOBILE = "mobile"; - // 2G network types - public static final String GSM = "gsm"; - public static final String GPRS = "gprs"; - public static final String EDGE = "edge"; - // 3G network types - public static final String CDMA = "cdma"; - public static final String UMTS = "umts"; - public static final String HSPA = "hspa"; - public static final String HSUPA = "hsupa"; - public static final String HSDPA = "hsdpa"; - public static final String ONEXRTT = "1xrtt"; - public static final String EHRPD = "ehrpd"; - // 4G network types - public static final String LTE = "lte"; - public static final String UMB = "umb"; - public static final String HSPA_PLUS = "hspa+"; - // return type - public static final String TYPE_UNKNOWN = "unknown"; - public static final String TYPE_ETHERNET = "ethernet"; - public static final String TYPE_WIFI = "wifi"; - public static final String TYPE_2G = "2g"; - public static final String TYPE_3G = "3g"; - public static final String TYPE_4G = "4g"; - public static final String TYPE_NONE = "none"; - - private static final String LOG_TAG = "NetworkManager"; - - private CallbackContext connectionCallbackContext; - private boolean registered = false; - - ConnectivityManager sockMan; - BroadcastReceiver receiver; - private String lastStatus = ""; - - /** - * Constructor. - */ - public NetworkManager() { - this.receiver = null; - } - - /** - * Sets the context of the Command. This can then be used to do things like - * get file paths associated with the Activity. - * - * @param cordova The context of the main Activity. - * @param webView The CordovaWebView Cordova is running in. - */ - public void initialize(CordovaInterface cordova, CordovaWebView webView) { - super.initialize(cordova, webView); - this.sockMan = (ConnectivityManager) cordova.getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); - this.connectionCallbackContext = null; - - // We need to listen to connectivity events to update navigator.connection - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - if (this.receiver == null) { - this.receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // (The null check is for the ARM Emulator, please use Intel Emulator for better results) - if(NetworkManager.this.webView != null) - updateConnectionInfo(sockMan.getActiveNetworkInfo()); - } - }; - cordova.getActivity().registerReceiver(this.receiver, intentFilter); - this.registered = true; - } - - } - - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackContext The callback id used when calling back into JavaScript. - * @return True if the action was valid, false otherwise. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) { - if (action.equals("getConnectionInfo")) { - this.connectionCallbackContext = callbackContext; - NetworkInfo info = sockMan.getActiveNetworkInfo(); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, this.getConnectionInfo(info)); - pluginResult.setKeepCallback(true); - callbackContext.sendPluginResult(pluginResult); - return true; - } - return false; - } - - /** - * Stop network receiver. - */ - public void onDestroy() { - if (this.receiver != null && this.registered) { - try { - this.cordova.getActivity().unregisterReceiver(this.receiver); - this.registered = false; - } catch (Exception e) { - Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e); - } - } - } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - /** - * Updates the JavaScript side whenever the connection changes - * - * @param info the current active network info - * @return - */ - private void updateConnectionInfo(NetworkInfo info) { - // send update to javascript "navigator.network.connection" - // Jellybean sends its own info - String thisStatus = this.getConnectionInfo(info); - if(!thisStatus.equals(lastStatus)) - { - sendUpdate(thisStatus); - lastStatus = thisStatus; - } - - } - - /** - * Get the latest network connection information - * - * @param info the current active network info - * @return a JSONObject that represents the network info - */ - private String getConnectionInfo(NetworkInfo info) { - String type = TYPE_NONE; - if (info != null) { - // If we are not connected to any network set type to none - if (!info.isConnected()) { - type = TYPE_NONE; - } - else { - type = getType(info); - } - } - Log.d("CordovaNetworkManager", "Connection Type: " + type); - return type; - } - - /** - * Create a new plugin result and send it back to JavaScript - * - * @param connection the network info to set as navigator.connection - */ - private void sendUpdate(String type) { - if (connectionCallbackContext != null) { - PluginResult result = new PluginResult(PluginResult.Status.OK, type); - result.setKeepCallback(true); - connectionCallbackContext.sendPluginResult(result); - } - webView.postMessage("networkconnection", type); - } - - /** - * Determine the type of connection - * - * @param info the network info so we can determine connection type. - * @return the type of mobile network we are on - */ - private String getType(NetworkInfo info) { - if (info != null) { - String type = info.getTypeName(); - - if (type.toLowerCase().equals(WIFI)) { - return TYPE_WIFI; - } - else if (type.toLowerCase().equals(MOBILE)) { - type = info.getSubtypeName(); - if (type.toLowerCase().equals(GSM) || - type.toLowerCase().equals(GPRS) || - type.toLowerCase().equals(EDGE)) { - return TYPE_2G; - } - else if (type.toLowerCase().startsWith(CDMA) || - type.toLowerCase().equals(UMTS) || - type.toLowerCase().equals(ONEXRTT) || - type.toLowerCase().equals(EHRPD) || - type.toLowerCase().equals(HSUPA) || - type.toLowerCase().equals(HSDPA) || - type.toLowerCase().equals(HSPA)) { - return TYPE_3G; - } - else if (type.toLowerCase().equals(LTE) || - type.toLowerCase().equals(UMB) || - type.toLowerCase().equals(HSPA_PLUS)) { - return TYPE_4G; - } - } - } - else { - return TYPE_NONE; - } - return TYPE_UNKNOWN; - } -} diff --git a/framework/src/org/apache/cordova/Notification.java b/framework/src/org/apache/cordova/Notification.java deleted file mode 100755 index cf2e95f8..00000000 --- a/framework/src/org/apache/cordova/Notification.java +++ /dev/null @@ -1,448 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaInterface; -import org.apache.cordova.api.CordovaPlugin; -import org.apache.cordova.api.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Vibrator; -import android.widget.EditText; - -/** - * This class provides access to notifications on the device. - */ -public class Notification extends CordovaPlugin { - - public int confirmResult = -1; - public ProgressDialog spinnerDialog = null; - public ProgressDialog progressDialog = null; - - /** - * Constructor. - */ - public Notification() { - } - - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArray of arguments for the plugin. - * @param callbackContext The callback context used when calling back into JavaScript. - * @return True when the action was valid, false otherwise. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - 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), callbackContext); - return true; - } - else if (action.equals("confirm")) { - this.confirm(args.getString(0), args.getString(1), args.getJSONArray(2), callbackContext); - return true; - } - else if (action.equals("prompt")) { - this.prompt(args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), callbackContext); - return true; - } - 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(); - } - else { - return false; - } - - // Only alert and confirm are async. - callbackContext.success(); - return true; - } - - //-------------------------------------------------------------------------- - // 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.cordova.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) { - } - } - } - } - } - - /** - * 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.cordova.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 callbackContext The callback context - */ - public synchronized void alert(final String message, final String title, final String buttonLabel, final CallbackContext callbackContext) { - - final CordovaInterface cordova = this.cordova; - - Runnable runnable = new Runnable() { - public void run() { - - AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity()); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(true); - dlg.setPositiveButton(buttonLabel, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0)); - } - }); - dlg.setOnCancelListener(new AlertDialog.OnCancelListener() { - public void onCancel(DialogInterface dialog) - { - dialog.dismiss(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0)); - } - }); - - dlg.create(); - dlg.show(); - }; - }; - this.cordova.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 callbackContext The callback context. - */ - public synchronized void confirm(final String message, final String title, final JSONArray buttonLabels, final CallbackContext callbackContext) { - - final CordovaInterface cordova = this.cordova; - - Runnable runnable = new Runnable() { - public void run() { - AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity()); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(true); - - // First button - if (buttonLabels.length() > 0) { - try { - dlg.setNegativeButton(buttonLabels.getString(0), - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1)); - } - }); - } catch (JSONException e) { } - } - - // Second button - if (buttonLabels.length() > 1) { - try { - dlg.setNeutralButton(buttonLabels.getString(1), - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2)); - } - }); - } catch (JSONException e) { } - } - - // Third button - if (buttonLabels.length() > 2) { - try { - dlg.setPositiveButton(buttonLabels.getString(2), - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3)); - } - }); - } catch (JSONException e) { } - } - dlg.setOnCancelListener(new AlertDialog.OnCancelListener() { - public void onCancel(DialogInterface dialog) - { - dialog.dismiss(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0)); - } - }); - - dlg.create(); - dlg.show(); - }; - }; - this.cordova.getActivity().runOnUiThread(runnable); - } - - /** - * Builds and shows a native Android prompt dialog with given title, message, buttons. - * This dialog only shows up to 3 buttons. Any labels after that will be ignored. - * The following results are returned to the JavaScript callback identified by callbackId: - * buttonIndex Index number of the button selected - * input1 The text entered in the prompt dialog box - * - * @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 callbackContext The callback context. - */ - public synchronized void prompt(final String message, final String title, final JSONArray buttonLabels, final String defaultText, final CallbackContext callbackContext) { - - final CordovaInterface cordova = this.cordova; - final EditText promptInput = new EditText(cordova.getActivity()); - promptInput.setHint(defaultText); - - Runnable runnable = new Runnable() { - public void run() { - AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity()); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(true); - - dlg.setView(promptInput); - - final JSONObject result = new JSONObject(); - - // First button - if (buttonLabels.length() > 0) { - try { - dlg.setNegativeButton(buttonLabels.getString(0), - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - try { - result.put("buttonIndex",1); - result.put("input1", promptInput.getText().toString().trim().length()==0 ? defaultText : promptInput.getText()); - } catch (JSONException e) { e.printStackTrace(); } - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result)); - } - }); - } catch (JSONException e) { } - } - - // Second button - if (buttonLabels.length() > 1) { - try { - dlg.setNeutralButton(buttonLabels.getString(1), - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - try { - result.put("buttonIndex",2); - result.put("input1", promptInput.getText().toString().trim().length()==0 ? defaultText : promptInput.getText()); - } catch (JSONException e) { e.printStackTrace(); } - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result)); - } - }); - } catch (JSONException e) { } - } - - // Third button - if (buttonLabels.length() > 2) { - try { - dlg.setPositiveButton(buttonLabels.getString(2), - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - try { - result.put("buttonIndex",3); - result.put("input1", promptInput.getText().toString().trim().length()==0 ? defaultText : promptInput.getText()); - } catch (JSONException e) { e.printStackTrace(); } - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result)); - } - }); - } catch (JSONException e) { } - } - dlg.setOnCancelListener(new AlertDialog.OnCancelListener() { - public void onCancel(DialogInterface dialog){ - dialog.dismiss(); - try { - result.put("buttonIndex",0); - result.put("input1", promptInput.getText().toString().trim().length()==0 ? defaultText : promptInput.getText()); - } catch (JSONException e) { e.printStackTrace(); } - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result)); - } - }); - - dlg.create(); - dlg.show(); - - }; - }; - this.cordova.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; - } - final CordovaInterface cordova = this.cordova; - Runnable runnable = new Runnable() { - public void run() { - Notification.this.spinnerDialog = ProgressDialog.show(cordova.getActivity(), title, message, true, true, - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - Notification.this.spinnerDialog = null; - } - }); - } - }; - this.cordova.getActivity().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; - } - final Notification notification = this; - final CordovaInterface cordova = this.cordova; - Runnable runnable = new Runnable() { - public void run() { - notification.progressDialog = new ProgressDialog(cordova.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.cordova.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); - } - } - - /** - * 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/SplashScreen.java b/framework/src/org/apache/cordova/SplashScreen.java deleted file mode 100644 index 66b5baeb..00000000 --- a/framework/src/org/apache/cordova/SplashScreen.java +++ /dev/null @@ -1,43 +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 org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.json.JSONArray; - -public class SplashScreen extends CordovaPlugin { - - @Override - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) { - if (action.equals("hide")) { - this.webView.postMessage("splashscreen", "hide"); - } else if (action.equals("show")){ - this.webView.postMessage("splashscreen", "show"); - } - else { - return false; - } - - callbackContext.success(); - return true; - } - -} diff --git a/framework/src/org/apache/cordova/Storage.java b/framework/src/org/apache/cordova/Storage.java deleted file mode 100755 index 34ebf387..00000000 --- a/framework/src/org/apache/cordova/Storage.java +++ /dev/null @@ -1,244 +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.io.File; - -import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.*; - -/** - * This class implements the HTML5 database support to work around a bug for - * Android 3.0 devices. It is not used for other versions of Android, since - * HTML5 database is built in to the browser. - */ -public class Storage extends CordovaPlugin { - - // Data Definition Language - private static final String ALTER = "alter"; - private static final String CREATE = "create"; - private static final String DROP = "drop"; - private static final String TRUNCATE = "truncate"; - - SQLiteDatabase myDb = null; // Database object - String path = null; // Database path - String dbName = null; // Database name - - /** - * Constructor. - */ - public Storage() { - } - - /** - * Executes the request and returns PluginResult. - * - * @param action - * The action to execute. - * @param args - * JSONArry of arguments for the plugin. - * @param callbackContext - * The callback context used when calling back into JavaScript. - * @return True if the action was valid, false otherwise. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - if (action.equals("openDatabase")) { - this.openDatabase(args.getString(0), args.getString(1), - args.getString(2), args.getLong(3)); - } else if (action.equals("executeSql")) { - String[] s = null; - if (args.isNull(1)) { - s = new String[0]; - } else { - JSONArray a = args.getJSONArray(1); - int len = a.length(); - s = new String[len]; - for (int i = 0; i < len; i++) { - s[i] = a.getString(i); - } - } - this.executeSql(args.getString(0), s, args.getString(2)); - } - else { - return false; - } - callbackContext.success(); - return true; - } - - /** - * Clean up and close database. - */ - @Override - public void onDestroy() { - if (this.myDb != null) { - this.myDb.close(); - this.myDb = null; - } - } - - /** - * Clean up on navigation/refresh. - */ - public void onReset() { - this.onDestroy(); - } - - // -------------------------------------------------------------------------- - // LOCAL METHODS - // -------------------------------------------------------------------------- - - /** - * Open database. - * - * @param db - * The name of the database - * @param version - * The version - * @param display_name - * The display name - * @param size - * The size in bytes - */ - public void openDatabase(String db, String version, String display_name, - long size) { - - // If database is open, then close it - if (this.myDb != null) { - this.myDb.close(); - } - - // If no database path, generate from application package - if (this.path == null) { - this.path = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); - } - - this.dbName = this.path + File.separator + db + ".db"; - - /* - * What is all this nonsense? Well the separator was incorrect so the db was showing up in the wrong - * directory. This bit of code fixes that issue and moves the db to the correct directory. - */ - File oldDbFile = new File(this.path + File.pathSeparator + db + ".db"); - if (oldDbFile.exists()) { - File dbPath = new File(this.path); - File dbFile = new File(dbName); - dbPath.mkdirs(); - oldDbFile.renameTo(dbFile); - } - - this.myDb = SQLiteDatabase.openOrCreateDatabase(this.dbName, null); - } - - /** - * Execute SQL statement. - * - * @param query - * The SQL query - * @param params - * Parameters for the query - * @param tx_id - * Transaction id - */ - public void executeSql(String query, String[] params, String tx_id) { - try { - if (isDDL(query)) { - this.myDb.execSQL(query); - this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', '');"); - } - else { - Cursor myCursor = this.myDb.rawQuery(query, params); - this.processResults(myCursor, tx_id); - myCursor.close(); - } - } - catch (SQLiteException ex) { - ex.printStackTrace(); - System.out.println("Storage.executeSql(): Error=" + ex.getMessage()); - - // Send error message back to JavaScript - this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').failQuery('" + ex.getMessage() + "','" + tx_id + "');"); - } - } - - /** - * Checks to see the the query is a Data Definition command - * - * @param query to be executed - * @return true if it is a DDL command, false otherwise - */ - private boolean isDDL(String query) { - String cmd = query.toLowerCase(); - if (cmd.startsWith(DROP) || cmd.startsWith(CREATE) || cmd.startsWith(ALTER) || cmd.startsWith(TRUNCATE)) { - return true; - } - return false; - } - - /** - * Process query results. - * - * @param cur - * Cursor into query results - * @param tx_id - * Transaction id - */ - public void processResults(Cursor cur, String tx_id) { - - String result = "[]"; - // If query result has rows - - if (cur.moveToFirst()) { - JSONArray fullresult = new JSONArray(); - String key = ""; - String value = ""; - int colCount = cur.getColumnCount(); - - // Build up JSON result object for each row - do { - JSONObject row = new JSONObject(); - try { - for (int i = 0; i < colCount; ++i) { - key = cur.getColumnName(i); - value = cur.getString(i); - row.put(key, value); - } - fullresult.put(row); - - } catch (JSONException e) { - e.printStackTrace(); - } - - } while (cur.moveToNext()); - - result = fullresult.toString(); - } - - // Let JavaScript know that there are no more rows - this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', " + result + ");"); - } - -}