mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-26 20:33:07 +08:00
ripped out plugins
This commit is contained in:
parent
227733d195
commit
adcbd879c8
@ -40,64 +40,9 @@
|
|||||||
<preference name="InAppBrowserStorageEnabled" value="true" />
|
<preference name="InAppBrowserStorageEnabled" value="true" />
|
||||||
<preference name="disallowOverscroll" value="true" />
|
<preference name="disallowOverscroll" value="true" />
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<feature name="App">
|
|
||||||
<param name="android-package" value="org.apache.cordova.App"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Geolocation">
|
|
||||||
<param name="android-package" value="org.apache.cordova.GeoBroker"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Device">
|
<feature name="Device">
|
||||||
<param name="android-package" value="org.apache.cordova.Device"/>
|
<param name="android-package" value="org.apache.cordova.Device"/>
|
||||||
</feature>
|
</feature>
|
||||||
<feature name="Accelerometer">
|
|
||||||
<param name="android-package" value="org.apache.cordova.AccelListener"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Compass">
|
|
||||||
<param name="android-package" value="org.apache.cordova.CompassListener"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Media">
|
|
||||||
<param name="android-package" value="org.apache.cordova.AudioHandler"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Camera">
|
|
||||||
<param name="android-package" value="org.apache.cordova.CameraLauncher"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Contacts">
|
|
||||||
<param name="android-package" value="org.apache.cordova.ContactManager"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="File">
|
|
||||||
<param name="android-package" value="org.apache.cordova.FileUtils"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="NetworkStatus">
|
|
||||||
<param name="android-package" value="org.apache.cordova.NetworkManager"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Notification">
|
|
||||||
<param name="android-package" value="org.apache.cordova.Notification"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Storage">
|
|
||||||
<param name="android-package" value="org.apache.cordova.Storage"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="FileTransfer">
|
|
||||||
<param name="android-package" value="org.apache.cordova.FileTransfer"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Capture">
|
|
||||||
<param name="android-package" value="org.apache.cordova.Capture"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Battery">
|
|
||||||
<param name="android-package" value="org.apache.cordova.BatteryListener"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="SplashScreen">
|
|
||||||
<param name="android-package" value="org.apache.cordova.SplashScreen"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Echo">
|
|
||||||
<param name="android-package" value="org.apache.cordova.Echo"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="Globalization">
|
|
||||||
<param name="android-package" value="org.apache.cordova.Globalization"/>
|
|
||||||
</feature>
|
|
||||||
<feature name="InAppBrowser">
|
|
||||||
<param name="android-package" value="org.apache.cordova.InAppBrowser"/>
|
|
||||||
</feature>
|
|
||||||
<!-- Deprecated plugins element. Remove in 3.0 -->
|
<!-- Deprecated plugins element. Remove in 3.0 -->
|
||||||
<plugins>
|
<plugins>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
@ -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<Sensor> 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<String, Object> params = new HashMap<String, Object>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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<String, AudioPlayer> players; // Audio player object
|
|
||||||
ArrayList<AudioPlayer> pausedForPhone; // Audio players that were paused when phone call came in
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*/
|
|
||||||
public AudioHandler() {
|
|
||||||
this.players = new HashMap<String, AudioPlayer>();
|
|
||||||
this.pausedForPhone = new ArrayList<AudioPlayer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<Sensor> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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<String,Boolean> 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<String,Boolean> buildPopulationSet(JSONArray fields) {
|
|
||||||
HashMap<String,Boolean> map = new HashMap<String,Boolean>();
|
|
||||||
|
|
||||||
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<fields.length(); i++) {
|
|
||||||
key = fields.getString(i);
|
|
||||||
if (key.startsWith("displayName")) {
|
|
||||||
map.put("displayName", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("name")) {
|
|
||||||
map.put("displayName", true);
|
|
||||||
map.put("name", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("nickname")) {
|
|
||||||
map.put("nickname", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("phoneNumbers")) {
|
|
||||||
map.put("phoneNumbers", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("emails")) {
|
|
||||||
map.put("emails", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("addresses")) {
|
|
||||||
map.put("addresses", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("ims")) {
|
|
||||||
map.put("ims", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("organizations")) {
|
|
||||||
map.put("organizations", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("birthday")) {
|
|
||||||
map.put("birthday", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("note")) {
|
|
||||||
map.put("note", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("urls")) {
|
|
||||||
map.put("urls", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("photos")) {
|
|
||||||
map.put("photos", true);
|
|
||||||
}
|
|
||||||
else if (key.startsWith("categories")) {
|
|
||||||
map.put("categories", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (JSONException e) {
|
|
||||||
Log.e(LOG_TAG, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method to get a string from a JSON object. Saves a
|
|
||||||
* lot of try/catch writing.
|
|
||||||
* If the property is not found in the object null will be returned.
|
|
||||||
*
|
|
||||||
* @param obj contact object to search
|
|
||||||
* @param property to be looked up
|
|
||||||
* @return The value of the property
|
|
||||||
*/
|
|
||||||
protected String getJsonString(JSONObject obj, String property) {
|
|
||||||
String value = null;
|
|
||||||
try {
|
|
||||||
if (obj != null) {
|
|
||||||
value = obj.getString(property);
|
|
||||||
if (value.equals("null")) {
|
|
||||||
Log.d(LOG_TAG, property + " is string called 'null'");
|
|
||||||
value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (JSONException e) {
|
|
||||||
Log.d(LOG_TAG, "Could not get = " + e.getMessage());
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles adding a JSON Contact object into the database.
|
|
||||||
* @return TODO
|
|
||||||
*/
|
|
||||||
public abstract String save(JSONObject contact);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles searching through SDK-specific contacts API.
|
|
||||||
*/
|
|
||||||
public abstract JSONArray search(JSONArray filter, JSONObject options);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles searching through SDK-specific contacts API.
|
|
||||||
* @throws JSONException
|
|
||||||
*/
|
|
||||||
public abstract JSONObject getContactById(String id) throws JSONException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles removing a contact from the database.
|
|
||||||
*/
|
|
||||||
public abstract boolean remove(String id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class that represents the where clause to be used in the database query
|
|
||||||
*/
|
|
||||||
class WhereOptions {
|
|
||||||
private String where;
|
|
||||||
private String[] whereArgs;
|
|
||||||
public void setWhere(String where) {
|
|
||||||
this.where = where;
|
|
||||||
}
|
|
||||||
public String getWhere() {
|
|
||||||
return where;
|
|
||||||
}
|
|
||||||
public void setWhereArgs(String[] whereArgs) {
|
|
||||||
this.whereArgs = whereArgs;
|
|
||||||
}
|
|
||||||
public String[] getWhereArgs() {
|
|
||||||
return whereArgs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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") + "}");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<String, RequestContext> activeRequests = new HashMap<String, RequestContext>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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) + "}");
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<String> namesList = new ArrayList<String>();
|
|
||||||
final Map<String,Integer> 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<String>() {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<String, Boolean> 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<String, Boolean> parseFeature(String optString) {
|
|
||||||
if (optString.equals(NULL)) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
HashMap<String, Boolean> map = new HashMap<String, Boolean>();
|
|
||||||
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<String, Boolean> 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://<callbackId>
|
|
||||||
*
|
|
||||||
* where <callbackId> 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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]");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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 + ");");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user