merging conflict with create script

This commit is contained in:
Anis Kadri 2012-09-28 17:43:26 -07:00
commit d406e2ed22
34 changed files with 1849 additions and 721 deletions

View File

@ -15,8 +15,8 @@ indicate that the project has yet to be fully endorsed by the ASF.
Requires Requires
--- ---
- Java JDK 1.5 - Java JDK 1.5 or greater
- Apache ANT - Apache ANT 1.8.0 or greater
- Android SDK [http://developer.android.com](http://developer.android.com) - Android SDK [http://developer.android.com](http://developer.android.com)
- Apache Commons Codec [http://commons.apache.org/codec/](http://commons.apache.org/codec/) - Apache Commons Codec [http://commons.apache.org/codec/](http://commons.apache.org/codec/)

View File

@ -30,10 +30,10 @@ then
exit 0 exit 0
fi fi
BUILD_PATH=$( cd "$( dirname "$0" )/.." && pwd ) BUILD_PATH="$( cd "$( dirname "$0" )/.." && pwd )"
VERSION=$(cat "$BUILD_PATH"/VERSION) VERSION=$(cat "$BUILD_PATH"/VERSION)
PROJECT_PATH=${1:-'./example'} PROJECT_PATH="${1:-'./example'}"
PACKAGE=${2:-"org.apache.cordova.example"} PACKAGE=${2:-"org.apache.cordova.example"}
ACTIVITY=${3:-"cordovaExample"} ACTIVITY=${3:-"cordovaExample"}
@ -87,19 +87,19 @@ function replace {
trap on_error ERR trap on_error ERR
trap on_exit EXIT trap on_exit EXIT
ANDROID_BIN=$( which android ) ANDROID_BIN="${ANDROID_BIN:=$( which android )}"
PACKAGE_AS_PATH=$(echo $PACKAGE | sed 's/\./\//g') PACKAGE_AS_PATH=$(echo $PACKAGE | sed 's/\./\//g')
ACTIVITY_PATH="$PROJECT_PATH"/src/$PACKAGE_AS_PATH/$ACTIVITY.java ACTIVITY_PATH="$PROJECT_PATH"/src/$PACKAGE_AS_PATH/$ACTIVITY.java
MANIFEST_PATH="$PROJECT_PATH"/AndroidManifest.xml MANIFEST_PATH="$PROJECT_PATH"/AndroidManifest.xml
TARGET=$($ANDROID_BIN list targets | grep id: | tail -1 | cut -f 2 -d ' ' ) TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
API_LEVEL=$($ANDROID_BIN list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ') API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ')
# if this a distribution release no need to build a jar # if this a distribution release no need to build a jar
if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ] if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ]
then then
# update the cordova-android framework for the desired target # update the cordova-android framework for the desired target
$ANDROID_BIN update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null "$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then
# Use curl to get the jar (TODO: Support Apache Mirrors) # Use curl to get the jar (TODO: Support Apache Mirrors)
@ -116,7 +116,7 @@ then
fi fi
# create new android project # create new android project
$ANDROID_BIN create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY &> /dev/null "$ANDROID_BIN" create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY &> /dev/null
# copy project template # copy project template
cp -r "$BUILD_PATH"/bin/templates/project/assets "$PROJECT_PATH" cp -r "$BUILD_PATH"/bin/templates/project/assets "$PROJECT_PATH"

View File

@ -163,40 +163,40 @@ if (!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.jar') &&
// copy in the project template // copy in the project template
WScript.Echo("Copying template files..."); WScript.Echo("Copying template files...");
exec('%comspec% /c xcopy '+ ROOT + '\\bin\\templates\\project\\res '+PROJECT_PATH+'\\res\\ /E /Y'); exec('%comspec% /c xcopy "'+ ROOT + '"\\bin\\templates\\project\\res '+PROJECT_PATH+'\\res\\ /E /Y');
exec('%comspec% /c xcopy '+ ROOT + '\\bin\\templates\\project\\assets '+PROJECT_PATH+'\\assets\\ /E /Y'); exec('%comspec% /c xcopy "'+ ROOT + '"\\bin\\templates\\project\\assets '+PROJECT_PATH+'\\assets\\ /E /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\project\\AndroidManifest.xml ' + PROJECT_PATH + '\\AndroidManifest.xml /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\project\\AndroidManifest.xml ' + PROJECT_PATH + '\\AndroidManifest.xml /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\project\\Activity.java '+ ACTIVITY_PATH +' /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\project\\Activity.java '+ ACTIVITY_PATH +' /Y');
// check if we have the source or the distro files // check if we have the source or the distro files
WScript.Echo("Copying js, jar & config.xml files..."); WScript.Echo("Copying js, jar & config.xml files...");
if(fso.FolderExists(ROOT + '\\framework')) { if(fso.FolderExists(ROOT + '\\framework')) {
exec('%comspec% /c copy '+ROOT+'\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y'); exec('%comspec% /c copy "'+ROOT+'"\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
exec('%comspec% /c copy '+ROOT+'\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y'); exec('%comspec% /c copy "'+ROOT+'"\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
fso.CreateFolder(PROJECT_PATH + '\\res\\xml'); fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
exec('%comspec% /c copy '+ROOT+'\\framework\\res\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y'); exec('%comspec% /c copy "'+ROOT+'"\\framework\\res\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
} else { } else {
// copy in cordova.js // copy in cordova.js
exec('%comspec% /c copy '+ROOT+'\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y'); exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
// copy in cordova.jar // copy in cordova.jar
exec('%comspec% /c copy '+ROOT+'\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y'); exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
// copy in xml // copy in xml
fso.CreateFolder(PROJECT_PATH + '\\res\\xml'); fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
exec('%comspec% /c copy '+ROOT+'\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y'); exec('%comspec% /c copy "'+ROOT+'"\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
} }
// copy cordova scripts // copy cordova scripts
fso.CreateFolder(PROJECT_PATH + '\\cordova'); fso.CreateFolder(PROJECT_PATH + '\\cordova');
createAppInfoJar(); createAppInfoJar();
WScript.Echo("Copying cordova command tools..."); WScript.Echo("Copying cordova command tools...");
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\debug.bat ' + PROJECT_PATH + '\\cordova\\debug.bat /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\debug.bat ' + PROJECT_PATH + '\\cordova\\debug.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\emulate.bat ' + PROJECT_PATH + '\\cordova\\emulate.bat /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\emulate.bat ' + PROJECT_PATH + '\\cordova\\emulate.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\BOOM.bat ' + PROJECT_PATH + '\\cordova\\BOOM.bat /Y'); exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\BOOM.bat ' + PROJECT_PATH + '\\cordova\\BOOM.bat /Y');
// interpolate the activity name and package // interpolate the activity name and package
WScript.Echo("Updating AndroidManifest.xml and Main Activity..."); WScript.Echo("Updating AndroidManifest.xml and Main Activity...");

View File

@ -88,14 +88,9 @@ create_project.on('exit', function(code) {
// TODO check that package name and activity name were substituted properly // TODO check that package name and activity name were substituted properly
}); });
// make sure plugins.xml was added // make sure config.xml was added
path.exists(util.format('%s/res/xml/plugins.xml', project_path), function(exists) { path.exists(util.format('%s/res/xml/config.xml', project_path), function(exists) {
assert(exists, 'plugins.xml did not get created'); assert(exists, 'config.xml did not get created');
});
// make sure cordova.xml was added
path.exists(util.format('%s/res/xml/cordova.xml', project_path), function(exists) {
assert(exists, 'plugins.xml did not get created');
}); });
// make sure cordova.jar was added // make sure cordova.jar was added

View File

@ -3,7 +3,7 @@
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="src" path="src"/> <classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/> <classpathentry kind="src" path="gen"/>
<classpathentry kind="lib" path="libs/commons-codec-1.6.jar"/> <classpathentry kind="lib" path="libs/commons-codec-1.7.jar"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/> <classpathentry kind="output" path="bin/classes"/>
</classpath> </classpath>

File diff suppressed because it is too large Load Diff

View File

@ -26,9 +26,37 @@
</filterchain> </filterchain>
</loadfile> </loadfile>
<!-- The local.properties file is created and updated by the 'android' tool. <!-- check that the version of ant is at least 1.8.0, as is needed
It contains the path to the SDK. It should *NOT* be checked into for the dblQuote property -->
Version Control Systems. --> <antversion property="thisantversion" atleast="1.8.0" />
<fail message="The required minimum version of ant is 1.8.0, you have ${ant.version}"
unless="thisantversion" />
<!-- check that commons codec is available. You should copy the codec jar to
framework/libs, as it is not included in the Cordova distribution.
The name of the jar file in framework/libs does not matter. -->
<available classname="org.apache.commons.codec.binary.Base64"
property="exists.base64"
ignoresystemclasses="true">
<classpath>
<pathelement path="${classpath}" />
<fileset dir="libs">
<include name="*.jar" />
</fileset>
</classpath>
</available>
<fail message="You need to put a copy of Apache Commons Codec jar in the framework/libs directory"
unless="exists.base64" />
<!-- The local.properties file is created and updated by the 'android'
tool. (For example "sdkdir/tools/android update project -p ." inside
of this directory where the AndroidManifest.xml file exists. This
properties file that gets built contains the path to the SDK. It
should *NOT* be checked into Version Control Systems since it holds
data about the local machine. -->
<available file="local.properties" property="exists.local.properties" />
<fail message="You need to create the file 'local.properties' by running 'android update project -p .' here."
unless="exists.local.properties" />
<loadproperties srcFile="local.properties" /> <loadproperties srcFile="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the <!-- The ant.properties file can be created by you. It is only edited by the
@ -66,13 +94,13 @@
application and should be checked into Version Control Systems. --> application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" /> <loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir --> <!-- quick check on sdk.dir -->
<fail <fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'" message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'"
unless="sdk.dir" unless="sdk.dir"
/> />
<!-- version-tag: custom --> <!-- version-tag: custom -->
<!-- extension targets. Uncomment the ones where you want to do custom work <!-- extension targets. Uncomment the ones where you want to do custom work
in between standard targets --> in between standard targets -->
<!-- <!--
@ -106,7 +134,7 @@
In all cases you must update the value of version-tag below to read 'custom' instead of an integer, In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project" in order to avoid having your file be overridden by tools such as "android update project"
--> -->
<import file="${sdk.dir}/tools/ant/build.xml" /> <import file="${sdk.dir}/tools/ant/build.xml" />
<!-- Combine JavaScript files into one cordova-uncompressed.js file. --> <!-- Combine JavaScript files into one cordova-uncompressed.js file. -->
<target name="build-javascript" depends="clean"> <target name="build-javascript" depends="clean">

View File

@ -51,6 +51,7 @@
<plugin name="Battery" value="org.apache.cordova.BatteryListener"/> <plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
<plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/> <plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
<plugin name="Echo" value="org.apache.cordova.Echo" /> <plugin name="Echo" value="org.apache.cordova.Echo" />
<plugin name="Globalization" value="org.apache.cordova.Globalization"/>
</plugins> </plugins>
</cordova> </cordova>

View File

@ -19,6 +19,7 @@
package org.apache.cordova; package org.apache.cordova;
import java.util.List; import java.util.List;
import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin; import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult; import org.apache.cordova.api.PluginResult;
@ -26,11 +27,11 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import android.content.Context;
import android.hardware.Sensor; import android.hardware.Sensor;
import android.hardware.SensorEvent; import android.hardware.SensorEvent;
import android.hardware.SensorEventListener; import android.hardware.SensorEventListener;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import android.content.Context;
/** /**
* This class listens to the accelerometer sensor and stores the latest * This class listens to the accelerometer sensor and stores the latest
@ -224,6 +225,16 @@ public class AccelListener extends Plugin implements SensorEventListener {
} }
} }
/**
* Called when the view navigates.
*/
@Override
public void onReset() {
if (this.status == AccelListener.RUNNING) {
this.stop();
}
}
// Sends an error back to JS // Sends an error back to JS
private void fail(int code, String message) { private void fail(int code, String message) {
// Error object // Error object

View File

@ -139,6 +139,14 @@ public class AudioHandler extends Plugin {
this.players.clear(); this.players.clear();
} }
/**
* Stop all audio players and recorders on navigate.
*/
@Override
public void onReset() {
onDestroy();
}
/** /**
* Called when a message is sent to plugin. * Called when a message is sent to plugin.
* *

View File

@ -99,6 +99,13 @@ public class BatteryListener extends Plugin {
removeBatteryListener(); removeBatteryListener();
} }
/**
* Stop battery receiver.
*/
public void onReset() {
removeBatteryListener();
}
/** /**
* Stop the battery receiver and set it to null. * Stop the battery receiver and set it to null.
*/ */

View File

@ -1,380 +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.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
/**
* This class provides a way for Java to run JavaScript in the web page that has loaded Cordova.
* The CallbackServer class implements an XHR server and a polling server with a list of JavaScript
* statements that are to be executed on the web page.
*
* The process flow for XHR is:
* 1. JavaScript makes an async XHR call.
* 2. The server holds the connection open until data is available.
* 3. The server writes the data to the client and closes the connection.
* 4. The server immediately starts listening for the next XHR call.
* 5. The client receives this XHR response, processes it.
* 6. The client sends a new async XHR request.
*
* The CallbackServer class requires the following permission in Android manifest file
* <uses-permission android:name="android.permission.INTERNET" />
*
* If the device has a proxy set, then XHR cannot be used, so polling must be used instead.
* This can be determined by the client by calling CallbackServer.usePolling().
*
* The process flow for polling is:
* 1. The client calls CallbackServer.getJavascript() to retrieve next statement.
* 2. If statement available, then client processes it.
* 3. The client repeats #1 in loop.
*/
public class CallbackServer implements Runnable {
@SuppressWarnings("unused")
private static final String LOG_TAG = "CallbackServer";
private ServerSocket waitSocket;
/**
* The list of JavaScript statements to be sent to JavaScript.
* This can be null when there are no messages available.
*/
private NativeToJsMessageQueue jsMessageQueue;
/**
* The port to listen on.
*/
private int port;
/**
* The server thread.
*/
private Thread serverThread;
/**
* Indicates the server is running.
*/
private boolean active;
/**
* Indicates that polling should be used instead of XHR.
*/
private boolean usePolling = true;
/**
* Security token to prevent other apps from accessing this callback server via XHR
*/
private String token;
/**
* Constructor.
*/
public CallbackServer() {
//Log.d(LOG_TAG, "CallbackServer()");
this.active = false;
this.port = 0;
}
/**
* Init callback server and start XHR if running local app.
*
* If Cordova app is loaded from file://, then we can use XHR
* otherwise we have to use polling due to cross-domain security restrictions.
*
* @param url The URL of the Cordova app being loaded
*/
public void init(String url) {
//System.out.println("CallbackServer.start("+url+")");
this.stopServer();
this.port = 0;
// Determine if XHR or polling is to be used
if ((url != null) && !url.startsWith("file://")) {
this.usePolling = true;
this.stopServer();
}
else if (android.net.Proxy.getDefaultHost() != null) {
this.usePolling = true;
this.stopServer();
}
else {
this.usePolling = false;
this.startServer();
}
}
/**
* Return if polling is being used instead of XHR.
* @return
*/
public boolean usePolling() {
return this.usePolling;
}
/**
* Get the port that this server is running on.
* @return
*/
public int getPort() {
return this.port;
}
/**
* Get the security token that this server requires when calling getJavascript().
* @return
*/
public String getToken() {
return this.token;
}
/**
* Start the server on a new thread.
*/
public void startServer() {
//Log.d(LOG_TAG, "CallbackServer.startServer()");
this.active = false;
// Start server on new thread
this.serverThread = new Thread(this);
this.serverThread.start();
}
/**
* Restart the server on a new thread.
*/
public void restartServer() {
// Stop server
this.stopServer();
// Start server again
this.startServer();
}
/**
* Start running the server.
* This is called automatically when the server thread is started.
*/
public void run() {
// Start server
try {
this.active = true;
String request;
waitSocket = new ServerSocket(0);
this.port = waitSocket.getLocalPort();
//Log.d(LOG_TAG, "CallbackServer -- using port " +this.port);
this.token = java.util.UUID.randomUUID().toString();
//Log.d(LOG_TAG, "CallbackServer -- using token "+this.token);
while (this.active) {
//Log.d(LOG_TAG, "CallbackServer: Waiting for data on socket");
Socket connection = waitSocket.accept();
BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()), 40);
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
request = xhrReader.readLine();
String response = "";
//Log.d(LOG_TAG, "CallbackServerRequest="+request);
if (this.active && (request != null)) {
if (request.contains("GET")) {
// Get requested file
String[] requestParts = request.split(" ");
// Must have security token
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
//Log.d(LOG_TAG, "CallbackServer -- Processing GET request");
String payload = null;
// Wait until there is some data to send, or send empty data every 10 sec
// to prevent XHR timeout on the client
while (this.active) {
if (jsMessageQueue != null) {
payload = jsMessageQueue.popAndEncode();
if (payload != null) {
break;
}
}
synchronized (this) {
try {
this.wait(10000); // prevent timeout from happening
//Log.d(LOG_TAG, "CallbackServer>>> break <<<");
break;
} catch (Exception e) {
}
}
}
// If server is still running
if (this.active) {
// If no data, then send 404 back to client before it times out
if (payload == null) {
//Log.d(LOG_TAG, "CallbackServer -- sending data 0");
response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
}
else {
//Log.d(LOG_TAG, "CallbackServer -- sending item");
response = "HTTP/1.1 200 OK\r\n\r\n";
response += encode(payload, "UTF-8");
}
}
else {
response = "HTTP/1.1 503 Service Unavailable\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 400 Bad Request\r\n\r\n ";
}
//Log.d(LOG_TAG, "CallbackServer: response="+response);
//Log.d(LOG_TAG, "CallbackServer: closing output");
output.writeBytes(response);
output.flush();
}
output.close();
xhrReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
this.active = false;
//Log.d(LOG_TAG, "CallbackServer.startServer() - EXIT");
}
/**
* Stop server.
* This stops the thread that the server is running on.
*/
public void stopServer() {
//Log.d(LOG_TAG, "CallbackServer.stopServer()");
if (this.active) {
this.active = false;
try { waitSocket.close(); } catch (IOException ignore) {}
// Break out of server wait
synchronized (this) {
this.notify();
}
}
}
/**
* Destroy
*/
public void destroy() {
this.stopServer();
}
public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
synchronized (this) {
this.jsMessageQueue = queue;
this.notify();
}
}
/* The Following code has been modified from original implementation of URLEncoder */
/* start */
/*
* 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.
*/
static final String digits = "0123456789ABCDEF";
/**
* This will encode the return value to JavaScript. We revert the encoding for
* common characters that don't require encoding to reduce the size of the string
* being passed to JavaScript.
*
* @param s to be encoded
* @param enc encoding type
* @return encoded string
*/
public static String encode(String s, String enc) throws UnsupportedEncodingException {
if (s == null || enc == null) {
throw new NullPointerException();
}
// check for UnsupportedEncodingException
"".getBytes(enc);
// Guess a bit bigger for encoded form
StringBuilder buf = new StringBuilder(s.length() + 16);
int start = -1;
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| " .-*_'(),<>=?@[]{}:~\"\\/;!".indexOf(ch) > -1) {
if (start >= 0) {
convert(s.substring(start, i), buf, enc);
start = -1;
}
if (ch != ' ') {
buf.append(ch);
} else {
buf.append(' ');
}
} else {
if (start < 0) {
start = i;
}
}
}
if (start >= 0) {
convert(s.substring(start, s.length()), buf, enc);
}
return buf.toString();
}
private static void convert(String s, StringBuilder buf, String enc) throws UnsupportedEncodingException {
byte[] bytes = s.getBytes(enc);
for (int j = 0; j < bytes.length; j++) {
buf.append('%');
buf.append(digits.charAt((bytes[j] & 0xf0) >> 4));
buf.append(digits.charAt(bytes[j] & 0xf));
}
}
/* end */
}

View File

@ -33,6 +33,8 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import android.content.Context; import android.content.Context;
import android.util.Log;
/** /**
* This class listens to the compass sensor and stores the latest heading value. * This class listens to the compass sensor and stores the latest heading value.
*/ */
@ -166,6 +168,13 @@ public class CompassListener extends Plugin implements SensorEventListener {
this.stop(); this.stop();
} }
/**
* Called when app has navigated and JS listeners have been destroyed.
*/
public void onReset() {
this.stop();
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// LOCAL METHODS // LOCAL METHODS
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------

View File

@ -226,24 +226,6 @@ public class CordovaChromeClient extends WebChromeClient {
result.confirm("OK"); result.confirm("OK");
} }
// Calling into CallbackServer
else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) {
String r = "";
if (message.equals("usePolling")) {
r = "" + this.appView.callbackServer.usePolling();
}
else if (message.equals("restartServer")) {
this.appView.callbackServer.restartServer();
}
else if (message.equals("getPort")) {
r = Integer.toString(this.appView.callbackServer.getPort());
}
else if (message.equals("getToken")) {
r = this.appView.callbackServer.getToken();
}
result.confirm(r);
}
// Show dialog // Show dialog
else { else {
final JsPromptResult res = result; final JsPromptResult res = result;

View File

@ -50,6 +50,8 @@ import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.WindowManager; import android.view.WindowManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebSettings.LayoutAlgorithm;
@ -65,7 +67,6 @@ public class CordovaWebView extends WebView {
private ArrayList<Integer> keyUpCodes = new ArrayList<Integer>(); private ArrayList<Integer> keyUpCodes = new ArrayList<Integer>();
public PluginManager pluginManager; public PluginManager pluginManager;
public CallbackServer callbackServer;
private boolean paused; private boolean paused;
private BroadcastReceiver receiver; private BroadcastReceiver receiver;
@ -572,12 +573,13 @@ public class CordovaWebView extends WebView {
// Check webview first to see if there is a history // Check webview first to see if there is a history
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior) // This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
if (super.canGoBack()) { if (super.canGoBack()) {
printBackForwardList();
super.goBack(); super.goBack();
return true; return true;
} }
// If our managed history has prev url // If our managed history has prev url
if (this.urls.size() > 1) { if (this.urls.size() > 1 && !this.useBrowserHistory) {
this.urls.pop(); // Pop current url this.urls.pop(); // Pop current url
String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl() String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl()
this.loadUrl(url); this.loadUrl(url);
@ -937,4 +939,17 @@ public class CordovaWebView extends WebView {
settings.setAllowUniversalAccessFromFileURLs(true); settings.setAllowUniversalAccessFromFileURLs(true);
} }
} }
public void printBackForwardList() {
WebBackForwardList currentList = this.copyBackForwardList();
int currentSize = currentList.getSize();
for(int i = 0; i < currentSize; ++i)
{
WebHistoryItem item = currentList.getItemAtIndex(i);
String url = item.getUrl();
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + "is " + url );
}
}
} }

View File

@ -23,20 +23,15 @@ import java.util.Hashtable;
import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.PluginResult; import org.apache.cordova.api.PluginResult;
import java.io.IOException;
import java.io.InputStream;
import org.apache.cordova.api.LOG; import org.apache.cordova.api.LOG;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.net.http.SslError; import android.net.http.SslError;
@ -44,7 +39,6 @@ import android.util.Log;
import android.view.View; import android.view.View;
import android.webkit.HttpAuthHandler; import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler; import android.webkit.SslErrorHandler;
import android.webkit.WebResourceResponse;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
@ -107,7 +101,7 @@ public class CordovaWebViewClient extends WebViewClient {
String action = url.substring(idx2 + 1, idx3); String action = url.substring(idx2 + 1, idx3);
String callbackId = url.substring(idx3 + 1, idx4); String callbackId = url.substring(idx3 + 1, idx4);
String jsonArgs = url.substring(idx4 + 1); String jsonArgs = url.substring(idx4 + 1);
appView.pluginManager.exec(service, action, callbackId, jsonArgs, true /* async */); appView.pluginManager.exec(service, action, callbackId, jsonArgs);
} }
/** /**
@ -261,14 +255,13 @@ public class CordovaWebViewClient extends WebViewClient {
// Flush stale messages. // Flush stale messages.
this.appView.jsMessageQueue.reset(); this.appView.jsMessageQueue.reset();
// Create callback server
if (this.appView.callbackServer == null) {
this.appView.callbackServer = new CallbackServer();
}
this.appView.callbackServer.init(url);
// Broadcast message that page has loaded // Broadcast message that page has loaded
this.appView.postMessage("onPageStarted", url); this.appView.postMessage("onPageStarted", url);
// Notify all plugins of the navigation, so they can clean up if necessary.
if (this.appView.pluginManager != null) {
this.appView.pluginManager.onReset();
}
} }
/** /**
@ -329,9 +322,6 @@ public class CordovaWebViewClient extends WebViewClient {
// Shutdown if blank loaded // Shutdown if blank loaded
if (url.equals("about:blank")) { if (url.equals("about:blank")) {
if (this.appView.callbackServer != null) {
this.appView.callbackServer.destroy();
}
appView.postMessage("exit", null); appView.postMessage("exit", null);
} }
} }

View File

@ -19,6 +19,8 @@
package org.apache.cordova; package org.apache.cordova;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.cordova.api.IPlugin; import org.apache.cordova.api.IPlugin;
import org.apache.cordova.api.LOG; import org.apache.cordova.api.LOG;
@ -142,6 +144,8 @@ public class DroidGap extends Activity implements CordovaInterface {
protected LinearLayout root; protected LinearLayout root;
protected boolean cancelLoadUrl = false; protected boolean cancelLoadUrl = false;
protected ProgressDialog spinnerDialog = null; protected ProgressDialog spinnerDialog = null;
private final ExecutorService threadPool = Executors.newCachedThreadPool();
// The initial URL for our app // The initial URL for our app
// ie http://server/path/index.html#abc?query // ie http://server/path/index.html#abc?query
@ -1051,4 +1055,9 @@ public class DroidGap extends Activity implements CordovaInterface {
} }
return null; return null;
} }
@Override
public ExecutorService getThreadPool() {
return threadPool;
}
} }

View File

@ -40,7 +40,7 @@ import org.json.JSONException;
public String exec(String service, String action, String callbackId, String arguments) throws JSONException { public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
jsMessageQueue.setPaused(true); jsMessageQueue.setPaused(true);
try { try {
boolean wasSync = pluginManager.exec(service, action, callbackId, arguments, true /* async */); boolean wasSync = pluginManager.exec(service, action, callbackId, arguments);
String ret = ""; String ret = "";
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) { if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) {
ret = jsMessageQueue.popAndEncode(); ret = jsMessageQueue.popAndEncode();

View File

@ -0,0 +1,63 @@
/*
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") + "}");
}
}

View File

@ -24,6 +24,7 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@ -32,6 +33,7 @@ import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
@ -58,34 +60,81 @@ public class FileTransfer extends Plugin {
private static final String LOG_TAG = "FileTransfer"; private static final String LOG_TAG = "FileTransfer";
private static final String LINE_START = "--"; private static final String LINE_START = "--";
private static final String LINE_END = "\r\n"; private static final String LINE_END = "\r\n";
private static final String BOUNDARY = "*****"; private static final String BOUNDARY = "+++++";
public static int FILE_NOT_FOUND_ERR = 1; public static int FILE_NOT_FOUND_ERR = 1;
public static int INVALID_URL_ERR = 2; public static int INVALID_URL_ERR = 2;
public static int CONNECTION_ERR = 3; public static int CONNECTION_ERR = 3;
public static int ABORTED_ERR = 4;
private static HashSet<String> abortTriggered = new HashSet<String>();
private SSLSocketFactory defaultSSLSocketFactory = null; private SSLSocketFactory defaultSSLSocketFactory = null;
private HostnameVerifier defaultHostnameVerifier = null; private HostnameVerifier defaultHostnameVerifier = null;
private static final class AbortException extends Exception {
private static final long serialVersionUID = 1L;
public AbortException(String str) {
super(str);
}
}
/**
* Works around a bug on Android 2.3.
* http://code.google.com/p/android/issues/detail?id=14562
*/
private static final class DoneHandlerInputStream extends FilterInputStream {
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;
}
}
/* (non-Javadoc) /* (non-Javadoc)
* @see org.apache.cordova.api.Plugin#execute(java.lang.String, org.json.JSONArray, java.lang.String) * @see org.apache.cordova.api.Plugin#execute(java.lang.String, org.json.JSONArray, java.lang.String)
*/ */
@Override @Override
public PluginResult execute(String action, JSONArray args, String callbackId) { public PluginResult execute(String action, JSONArray args, String callbackId) {
String source = null; if (action.equals("upload") || action.equals("download")) {
String target = null; String source = null;
try { String target = null;
source = args.getString(0); try {
target = args.getString(1); source = args.getString(0);
} catch (JSONException e) { target = args.getString(1);
Log.d(LOG_TAG, "Missing source or target"); } catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target"); Log.d(LOG_TAG, "Missing source or target");
} return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target");
}
if (action.equals("upload")) { if (action.equals("upload")) {
return upload(URLDecoder.decode(source), target, args); return upload(URLDecoder.decode(source), target, args, callbackId);
} else if (action.equals("download")) { } else {
return download(source, target, args.optBoolean(2)); return download(source, target, args, callbackId);
}
} else if (action.equals("abort")) {
return abort(args);
} else { } else {
return new PluginResult(PluginResult.Status.INVALID_ACTION); return new PluginResult(PluginResult.Status.INVALID_ACTION);
} }
@ -96,6 +145,7 @@ public class FileTransfer extends Plugin {
* @param source Full path of the file on the file system * @param source Full path of the file on the file system
* @param target URL of the server to receive the file * @param target URL of the server to receive the file
* @param args JSON Array of args * @param args JSON Array of args
* @param callbackId callback id for optional progress reports
* *
* args[2] fileKey Name of file request parameter * args[2] fileKey Name of file request parameter
* args[3] fileName File name to be used on server * args[3] fileName File name to be used on server
@ -103,7 +153,7 @@ public class FileTransfer extends Plugin {
* args[5] params key:value pairs of user-defined parameters * args[5] params key:value pairs of user-defined parameters
* @return FileUploadResult containing result of upload request * @return FileUploadResult containing result of upload request
*/ */
private PluginResult upload(String source, String target, JSONArray args) { private PluginResult upload(String source, String target, JSONArray args, String callbackId) {
Log.d(LOG_TAG, "upload " + source + " to " + target); Log.d(LOG_TAG, "upload " + source + " to " + target);
HttpURLConnection conn = null; HttpURLConnection conn = null;
@ -121,6 +171,7 @@ public class FileTransfer extends Plugin {
if (headers == null && params != null) { if (headers == null && params != null) {
headers = params.optJSONObject("headers"); headers = params.optJSONObject("headers");
} }
String objectId = args.getString(9);
Log.d(LOG_TAG, "fileKey: " + fileKey); Log.d(LOG_TAG, "fileKey: " + fileKey);
Log.d(LOG_TAG, "fileName: " + fileName); Log.d(LOG_TAG, "fileName: " + fileName);
@ -129,12 +180,14 @@ public class FileTransfer extends Plugin {
Log.d(LOG_TAG, "trustEveryone: " + trustEveryone); Log.d(LOG_TAG, "trustEveryone: " + trustEveryone);
Log.d(LOG_TAG, "chunkedMode: " + chunkedMode); Log.d(LOG_TAG, "chunkedMode: " + chunkedMode);
Log.d(LOG_TAG, "headers: " + headers); Log.d(LOG_TAG, "headers: " + headers);
Log.d(LOG_TAG, "objectId: " + objectId);
// Create return object // Create return object
FileUploadResult result = new FileUploadResult(); FileUploadResult result = new FileUploadResult();
FileProgressResult progress = new FileProgressResult();
// Get a input stream of the file on the phone // Get a input stream of the file on the phone
FileInputStream fileInputStream = (FileInputStream) getPathFromUri(source); InputStream inputStream = getPathFromUri(source);
DataOutputStream dos = null; DataOutputStream dos = null;
@ -242,12 +295,18 @@ public class FileTransfer extends Plugin {
int stringLength = extraBytes.length + midParams.length() + tailParams.length() + fileNameBytes.length; int stringLength = extraBytes.length + midParams.length() + tailParams.length() + fileNameBytes.length;
Log.d(LOG_TAG, "String Length: " + stringLength); Log.d(LOG_TAG, "String Length: " + stringLength);
int fixedLength = (int) fileInputStream.getChannel().size() + stringLength; int fixedLength = -1;
if (inputStream instanceof FileInputStream) {
fixedLength = (int) ((FileInputStream)inputStream).getChannel().size() + stringLength;
progress.setLengthComputable(true);
progress.setTotal(fixedLength);
}
Log.d(LOG_TAG, "Content Length: " + fixedLength); Log.d(LOG_TAG, "Content Length: " + fixedLength);
// setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices. // setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices.
// http://code.google.com/p/android/issues/detail?id=3164 // http://code.google.com/p/android/issues/detail?id=3164
// It also causes OOM if HTTPS is used, even on newer devices. // It also causes OOM if HTTPS is used, even on newer devices.
chunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps); chunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps);
chunkedMode = chunkedMode || (fixedLength == -1);
if (chunkedMode) { if (chunkedMode) {
conn.setChunkedStreamingMode(maxBufferSize); conn.setChunkedStreamingMode(maxBufferSize);
@ -265,12 +324,12 @@ public class FileTransfer extends Plugin {
dos.writeBytes(midParams); dos.writeBytes(midParams);
// create a buffer of maximum size // create a buffer of maximum size
bytesAvailable = fileInputStream.available(); bytesAvailable = inputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize); bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize]; buffer = new byte[bufferSize];
// read file and write it into form... // read file and write it into form...
bytesRead = fileInputStream.read(buffer, 0, bufferSize); bytesRead = inputStream.read(buffer, 0, bufferSize);
totalBytes = 0; totalBytes = 0;
long prevBytesRead = 0; long prevBytesRead = 0;
@ -282,28 +341,36 @@ public class FileTransfer extends Plugin {
prevBytesRead = totalBytes; prevBytesRead = totalBytes;
Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes"); Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes");
} }
bytesAvailable = fileInputStream.available(); bytesAvailable = inputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize); bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize); bytesRead = inputStream.read(buffer, 0, bufferSize);
if (objectId != null) {
// Only send progress callbacks if the JS code sent us an object ID,
// so we don't spam old versions with unrecognized callbacks.
progress.setLoaded(totalBytes);
PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
progressResult.setKeepCallback(true);
success(progressResult, callbackId);
}
synchronized (abortTriggered) {
if (objectId != null && abortTriggered.contains(objectId)) {
abortTriggered.remove(objectId);
throw new AbortException("upload aborted");
}
}
} }
// send multipart form data necessary after file data... // send multipart form data necessary after file data...
dos.writeBytes(tailParams); dos.writeBytes(tailParams);
// close streams // close streams
fileInputStream.close(); inputStream.close();
dos.flush(); dos.flush();
dos.close(); dos.close();
//------------------ read the SERVER RESPONSE //------------------ read the SERVER RESPONSE
StringBuffer responseString = new StringBuffer(""); StringBuffer responseString = new StringBuffer("");
DataInputStream inStream; DataInputStream inStream = new DataInputStream(getInputStream(conn));
try {
inStream = new DataInputStream ( conn.getInputStream() );
} catch(FileNotFoundException e) {
Log.e(LOG_TAG, e.toString(), e);
throw new IOException("Received error from server");
}
String line; String line;
while (( line = inStream.readLine()) != null) { while (( line = inStream.readLine()) != null) {
@ -342,6 +409,9 @@ public class FileTransfer extends Plugin {
} catch (JSONException e) { } catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e); Log.e(LOG_TAG, e.getMessage(), e);
return new PluginResult(PluginResult.Status.JSON_EXCEPTION); return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
} catch (AbortException e) {
JSONObject error = createFileTransferError(ABORTED_ERR, source, target, conn);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (Throwable t) { } catch (Throwable t) {
// Shouldn't happen, but will // Shouldn't happen, but will
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
@ -354,6 +424,13 @@ public class FileTransfer extends Plugin {
} }
} }
private InputStream getInputStream(HttpURLConnection conn) throws IOException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return new DoneHandlerInputStream(conn.getInputStream());
}
return conn.getInputStream();
}
// always verify the host - don't check for certificate // always verify the host - don't check for certificate
final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) { public boolean verify(String hostname, SSLSession session) {
@ -459,11 +536,13 @@ public class FileTransfer extends Plugin {
* @param target Full path of the file on the file system * @param target Full path of the file on the file system
* @return JSONObject the downloaded file * @return JSONObject the downloaded file
*/ */
private PluginResult download(String source, String target, boolean trustEveryone) { private PluginResult download(String source, String target, JSONArray args, String callbackId) {
Log.d(LOG_TAG, "download " + source + " to " + target); Log.d(LOG_TAG, "download " + source + " to " + target);
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
boolean trustEveryone = args.optBoolean(2);
String objectId = args.getString(3);
File file = getFileFromPath(target); File file = getFileFromPath(target);
// create needed directories // create needed directories
@ -513,22 +592,39 @@ public class FileTransfer extends Plugin {
connection.connect(); connection.connect();
Log.d(LOG_TAG, "Download file:" + url); Log.d(LOG_TAG, "Download file:" + url);
InputStream inputStream; InputStream inputStream = getInputStream(connection);
try {
inputStream = connection.getInputStream();
} catch(FileNotFoundException e) {
Log.e(LOG_TAG, e.toString(), e);
throw new IOException("Received error from server");
}
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int bytesRead = 0; int bytesRead = 0;
long totalBytes = 0;
FileProgressResult progress = new FileProgressResult();
if (connection.getContentEncoding() == null) {
// Only trust content-length header if no gzip etc
progress.setLengthComputable(true);
progress.setTotal(connection.getContentLength());
}
FileOutputStream outputStream = new FileOutputStream(file); FileOutputStream outputStream = new FileOutputStream(file);
// write bytes to file // write bytes to file
while ((bytesRead = inputStream.read(buffer)) > 0) { while ((bytesRead = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, bytesRead); outputStream.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
if (objectId != null) {
// Only send progress callbacks if the JS code sent us an object ID,
// so we don't spam old versions with unrecognized callbacks.
progress.setLoaded(totalBytes);
PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
progressResult.setKeepCallback(true);
success(progressResult, callbackId);
}
synchronized (abortTriggered) {
if (objectId != null && abortTriggered.contains(objectId)) {
abortTriggered.remove(objectId);
throw new AbortException("download aborted");
}
}
} }
outputStream.close(); outputStream.close();
@ -554,6 +650,9 @@ public class FileTransfer extends Plugin {
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} }
} catch (AbortException e) {
JSONObject error = createFileTransferError(ABORTED_ERR, source, target, connection);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection); JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection);
Log.d(LOG_TAG, "I got a file not found exception"); Log.d(LOG_TAG, "I got a file not found exception");
@ -621,4 +720,23 @@ public class FileTransfer extends Plugin {
return file; return file;
} }
/**
* Abort an ongoing upload or download.
*
* @param args args
*/
private PluginResult abort(JSONArray args) {
String objectId;
try {
objectId = args.getString(0);
} catch (JSONException e) {
Log.d(LOG_TAG, "Missing objectId");
return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing objectId");
}
synchronized (abortTriggered) {
abortTriggered.add(objectId);
}
return new PluginResult(PluginResult.Status.OK);
}
} }

View File

@ -29,6 +29,7 @@ public class FileUploadResult {
private long bytesSent = 0; // bytes sent private long bytesSent = 0; // bytes sent
private int responseCode = -1; // HTTP response code private int responseCode = -1; // HTTP response code
private String response = null; // HTTP response private String response = null; // HTTP response
private String objectId = null; // FileTransfer object id
public long getBytesSent() { public long getBytesSent() {
return bytesSent; return bytesSent;
@ -54,10 +55,19 @@ public class FileUploadResult {
this.response = response; this.response = response;
} }
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public JSONObject toJSONObject() throws JSONException { public JSONObject toJSONObject() throws JSONException {
return new JSONObject( return new JSONObject(
"{bytesSent:" + bytesSent + "{bytesSent:" + bytesSent +
",responseCode:" + responseCode + ",responseCode:" + responseCode +
",response:" + JSONObject.quote(response) + "}"); ",response:" + JSONObject.quote(response) +
",objectId:" + JSONObject.quote(objectId) + "}");
} }
} }

View File

@ -1048,10 +1048,16 @@ public class FileUtils extends Plugin {
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
protected static String getRealPathFromURI(Uri contentUri, CordovaInterface cordova) { protected static String getRealPathFromURI(Uri contentUri, CordovaInterface cordova) {
String[] proj = { _DATA }; String uri = contentUri.toString();
Cursor cursor = cordova.getActivity().managedQuery(contentUri, proj, null, null, null); if (uri.startsWith("content:")) {
int column_index = cursor.getColumnIndexOrThrow(_DATA); String[] proj = { _DATA };
cursor.moveToFirst(); Cursor cursor = cordova.getActivity().managedQuery(contentUri, proj, null, null, null);
return cursor.getString(column_index); int column_index = cursor.getColumnIndexOrThrow(_DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} else {
return uri;
}
} }
} }

View File

@ -135,10 +135,22 @@ public class GeoBroker extends Plugin {
* Stop listener. * Stop listener.
*/ */
public void onDestroy() { public void onDestroy() {
this.networkListener.destroy(); if (this.networkListener != null) {
this.gpsListener.destroy(); this.networkListener.destroy();
this.networkListener = null; this.networkListener = null;
this.gpsListener = 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) { public JSONObject returnLocationJSON(Location loc) {

View File

@ -0,0 +1,579 @@
/*
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.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.text.format.Time;
/**
*
*/
public class Globalization extends Plugin {
//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 PluginResult execute(String action, JSONArray data, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
JSONObject obj = new JSONObject();
try{
if (action.equals(GETLOCALENAME)){
obj = getLocaleName();
return new PluginResult(status, obj);
}else if (action.equals(GETPREFERREDLANGUAGE)){
obj = getPreferredLanguage();
return new PluginResult(status, obj);
} else if (action.equalsIgnoreCase(DATETOSTRING)) {
obj = getDateToString(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(STRINGTODATE)){
obj = getStringtoDate(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(GETDATEPATTERN)){
obj = getDatePattern(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(GETDATENAMES)){
obj = getDateNames(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(ISDAYLIGHTSAVINGSTIME)){
obj = getIsDayLightSavingsTime(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(GETFIRSTDAYOFWEEK)){
obj = getFirstDayOfWeek(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(NUMBERTOSTRING)){
obj = getNumberToString(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(STRINGTONUMBER)){
obj = getStringToNumber(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(GETNUMBERPATTERN)){
obj = getNumberPattern(data);
return new PluginResult(PluginResult.Status.OK, obj);
}else if(action.equalsIgnoreCase(GETCURRENCYPATTERN)){
obj = getCurrencyPattern(data);
return new PluginResult(PluginResult.Status.OK, obj);
}
}catch (GlobalizationError ge){
return new PluginResult(PluginResult.Status.ERROR, ge.toJson());
}catch (Exception e){
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
return new PluginResult(PluginResult.Status.INVALID_ACTION);
}
/*
* @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
*/
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;
}
}

View File

@ -0,0 +1,108 @@
/*
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;
}
}

View File

@ -36,7 +36,7 @@ public class NativeToJsMessageQueue {
private static final String LOG_TAG = "JsMessageQueue"; private static final String LOG_TAG = "JsMessageQueue";
// This must match the default value in incubator-cordova-js/lib/android/exec.js // This must match the default value in incubator-cordova-js/lib/android/exec.js
private static final int DEFAULT_BRIDGE_MODE = 1; private static final int DEFAULT_BRIDGE_MODE = 3;
// Set this to true to force plugin results to be encoding as // Set this to true to force plugin results to be encoding as
// JS instead of the custom format (useful for benchmarking). // JS instead of the custom format (useful for benchmarking).
@ -80,12 +80,11 @@ public class NativeToJsMessageQueue {
public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) { public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) {
this.cordova = cordova; this.cordova = cordova;
this.webView = webView; this.webView = webView;
registeredListeners = new BridgeMode[5]; registeredListeners = new BridgeMode[4];
registeredListeners[0] = null; // Polling. Requires no logic. registeredListeners[0] = null; // Polling. Requires no logic.
registeredListeners[1] = new CallbackBridgeMode(); registeredListeners[1] = new LoadUrlBridgeMode();
registeredListeners[2] = new LoadUrlBridgeMode(); registeredListeners[2] = new OnlineEventsBridgeMode();
registeredListeners[3] = new OnlineEventsBridgeMode(); registeredListeners[3] = new PrivateApiBridgeMode();
registeredListeners[4] = new PrivateApiBridgeMode();
reset(); reset();
} }
@ -270,15 +269,6 @@ public class NativeToJsMessageQueue {
void onNativeToJsMessageAvailable(); void onNativeToJsMessageAvailable();
} }
/** Uses a local server to send messages to JS via an XHR */
private class CallbackBridgeMode implements BridgeMode {
public void onNativeToJsMessageAvailable() {
if (webView.callbackServer != null) {
webView.callbackServer.onNativeToJsMessageAvailable(NativeToJsMessageQueue.this);
}
}
}
/** Uses webView.loadUrl("javascript:") to execute messages. */ /** Uses webView.loadUrl("javascript:") to execute messages. */
private class LoadUrlBridgeMode implements BridgeMode { private class LoadUrlBridgeMode implements BridgeMode {
final Runnable runnable = new Runnable() { final Runnable runnable = new Runnable() {

View File

@ -69,6 +69,7 @@ public class NetworkManager extends Plugin {
private static final String LOG_TAG = "NetworkManager"; private static final String LOG_TAG = "NetworkManager";
private String connectionCallbackId; private String connectionCallbackId;
private boolean registered = false;
ConnectivityManager sockMan; ConnectivityManager sockMan;
BroadcastReceiver receiver; BroadcastReceiver receiver;
@ -99,10 +100,13 @@ public class NetworkManager extends Plugin {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)); // (The null check is for the ARM Emulator, please use Intel Emulator for better results)
if(webView != null)
updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO));
} }
}; };
cordova.getActivity().registerReceiver(this.receiver, intentFilter); cordova.getActivity().registerReceiver(this.receiver, intentFilter);
this.registered = true;
} }
} }
@ -144,15 +148,23 @@ public class NetworkManager extends Plugin {
* Stop network receiver. * Stop network receiver.
*/ */
public void onDestroy() { public void onDestroy() {
if (this.receiver != null) { if (this.receiver != null && this.registered) {
try { try {
this.cordova.getActivity().unregisterReceiver(this.receiver); this.cordova.getActivity().unregisterReceiver(this.receiver);
this.registered = false;
} catch (Exception e) { } catch (Exception e) {
Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e); Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e);
} }
} }
} }
/**
* Stop the network receiver on navigation.
*/
public void onReset() {
this.onDestroy();
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// LOCAL METHODS // LOCAL METHODS
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -198,10 +210,9 @@ public class NetworkManager extends Plugin {
result.setKeepCallback(true); result.setKeepCallback(true);
this.success(result, this.connectionCallbackId); this.success(result, this.connectionCallbackId);
// Send to all plugins
webView.postMessage("networkconnection", type); webView.postMessage("networkconnection", type);
} }
/** /**
* Determine the type of connection * Determine the type of connection
* *

View File

@ -115,6 +115,13 @@ public class Storage extends Plugin {
} }
} }
/**
* Clean up on navigation/refresh.
*/
public void onReset() {
this.onDestroy();
}
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// LOCAL METHODS // LOCAL METHODS
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------

View File

@ -82,6 +82,13 @@ public class TempListener extends Plugin implements SensorEventListener {
this.stop(); this.stop();
} }
/**
* Called on navigation.
*/
public void onReset() {
this.stop();
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// LOCAL METHODS // LOCAL METHODS
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------

View File

@ -22,6 +22,8 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import java.util.concurrent.ExecutorService;
/** /**
* The Cordova activity abstract class that is extended by DroidGap. * The Cordova activity abstract class that is extended by DroidGap.
* It is used to isolate plugin development, and remove dependency on entire Cordova library. * It is used to isolate plugin development, and remove dependency on entire Cordova library.
@ -67,5 +69,9 @@ public interface CordovaInterface {
* @return Object or null * @return Object or null
*/ */
public Object onMessage(String id, Object data); public Object onMessage(String id, Object data);
/**
* Returns a shared thread pool that can be used for background tasks.
*/
public ExecutorService getThreadPool();
} }

View File

@ -116,4 +116,11 @@ public interface IPlugin {
* @return Return true to prevent the URL from loading. Default is false. * @return Return true to prevent the URL from loading. Default is false.
*/ */
boolean onOverrideUrlLoading(String url); boolean onOverrideUrlLoading(String url);
/**
* Called when the WebView does a top-level navigation or refreshes.
*
* Plugins should stop any long-running processes and clean up internal state.
*/
void onReset();
} }

View File

@ -29,6 +29,8 @@ import android.content.res.AssetManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.util.Log; import android.util.Log;
import java.util.concurrent.ExecutorService;
@Deprecated @Deprecated
public class LegacyContext implements CordovaInterface { public class LegacyContext implements CordovaInterface {
private static final String LOG_TAG = "Deprecation Notice"; private static final String LOG_TAG = "Deprecation Notice";
@ -145,4 +147,10 @@ public class LegacyContext implements CordovaInterface {
Log.i(LOG_TAG, "Replace ctx.unbindService() with cordova.getActivity().unbindService()"); Log.i(LOG_TAG, "Replace ctx.unbindService() with cordova.getActivity().unbindService()");
this.cordova.getActivity().unbindService(conn); this.cordova.getActivity().unbindService(conn);
} }
@Override
public ExecutorService getThreadPool() {
Log.i(LOG_TAG, "Replace ctx.getThreadPool() with cordova.getThreadPool()");
return this.cordova.getThreadPool();
}
} }

View File

@ -23,6 +23,8 @@ import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import android.content.Intent; import android.content.Intent;
import android.util.Log;
/** /**
* Plugin interface must be implemented by any plugin classes. * Plugin interface must be implemented by any plugin classes.
* *
@ -215,4 +217,14 @@ public abstract class Plugin implements IPlugin {
public void error(String message, String callbackId) { public void error(String message, String callbackId) {
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId); this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId);
} }
/**
* Called when the WebView does a top-level navigation or refreshes.
*
* Plugins should stop any long-running processes and clean up internal state.
*
* Does nothing by default.
*/
public void onReset() {
}
} }

View File

@ -47,7 +47,6 @@ public class PluginManager {
private final CordovaInterface ctx; private final CordovaInterface ctx;
private final CordovaWebView app; private final CordovaWebView app;
private final ExecutorService execThreadPool = Executors.newCachedThreadPool();
// Flag to track first time through // Flag to track first time through
private boolean firstRun; private boolean firstRun;
@ -210,23 +209,19 @@ public class PluginManager {
* this is an async plugin call. * this is an async plugin call.
* @param args An Array literal string containing any arguments needed in the * @param args An Array literal string containing any arguments needed in the
* plugin execute method. * plugin execute method.
* @param async Boolean indicating whether the calling JavaScript code is expecting an
* immediate return value. If true, either Cordova.callbackSuccess(...) or
* Cordova.callbackError(...) is called once the plugin code has executed.
* @return Whether the task completed synchronously. * @return Whether the task completed synchronously.
*/ */
public boolean exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async) { public boolean exec(final String service, final String action, final String callbackId, final String jsonArgs) {
PluginResult cr = null; PluginResult cr = null;
boolean runAsync = async; final IPlugin plugin = this.getPlugin(service);
boolean runAsync = !plugin.isSynch(action);
try { try {
final JSONArray args = new JSONArray(jsonArgs); final JSONArray args = new JSONArray(jsonArgs);
final IPlugin plugin = this.getPlugin(service);
//final CordovaInterface ctx = this.ctx; //final CordovaInterface ctx = this.ctx;
if (plugin != null) { if (plugin != null) {
runAsync = async && !plugin.isSynch(action);
if (runAsync) { if (runAsync) {
// Run this on a different thread so that this one can return back to JS // Run this on a different thread so that this one can return back to JS
execThreadPool.execute(new Runnable() { ctx.getThreadPool().execute(new Runnable() {
public void run() { public void run() {
try { try {
// Call execute on the plugin so that it can do it's thing // Call execute on the plugin so that it can do it's thing
@ -267,6 +262,11 @@ public class PluginManager {
return true; return true;
} }
@Deprecated
public boolean exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
return exec(service, action, callbackId, jsonArgs);
}
/** /**
* Get the plugin object that implements the service. * Get the plugin object that implements the service.
* If the plugin object does not already exist, then create it. * If the plugin object does not already exist, then create it.
@ -397,6 +397,20 @@ public class PluginManager {
return false; return false;
} }
/**
* Called when the app navigates or refreshes.
*/
public void onReset() {
Iterator<PluginEntry> it = this.entries.values().iterator();
while (it.hasNext()) {
IPlugin plugin = it.next().plugin;
if (plugin != null) {
plugin.onReset();
}
}
}
private void pluginConfigurationMissing() { private void pluginConfigurationMissing() {
LOG.e(TAG, "====================================================================================="); LOG.e(TAG, "=====================================================================================");
LOG.e(TAG, "ERROR: plugin.xml is missing. Add res/xml/plugins.xml to your project."); LOG.e(TAG, "ERROR: plugin.xml is missing. Add res/xml/plugins.xml to your project.");