From 7eb3e5d13955725f44f72cb816c2fc2b2f376e18 Mon Sep 17 00:00:00 2001 From: filmaj <maj.fil@gmail.com> Date: Wed, 16 May 2012 19:38:11 -0700 Subject: [PATCH 01/13] [CB-659] create script should work on android --- bin/create.bat | 2 +- bin/create.js | 61 ++++++++++++++------ bin/templates/project/cordova/create.bat | 2 + bin/templates/project/cordova/create.js | 70 +++++++++++++++++++++++ bin/templates/project/cordova/debug.bat | 0 bin/templates/project/cordova/emulate.bat | 0 bin/templates/project/cordova/log.bat | 0 7 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 bin/templates/project/cordova/create.bat create mode 100644 bin/templates/project/cordova/create.js create mode 100644 bin/templates/project/cordova/debug.bat create mode 100644 bin/templates/project/cordova/emulate.bat create mode 100644 bin/templates/project/cordova/log.bat diff --git a/bin/create.bat b/bin/create.bat index b9c9392b..7182d21e 100644 --- a/bin/create.bat +++ b/bin/create.bat @@ -1 +1 @@ -cscript create.js \ No newline at end of file +cscript bin\create.js %* \ No newline at end of file diff --git a/bin/create.js b/bin/create.js index 24e70325..96e61eca 100644 --- a/bin/create.js +++ b/bin/create.js @@ -25,8 +25,9 @@ */ function read(filename) { + WScript.Echo('Reading in ' + filename); var fso=WScript.CreateObject("Scripting.FileSystemObject"); - var f=fso.OpenTextFile(filename, 1, true); + var f=fso.OpenTextFile(filename, 1); var s=f.ReadAll(); f.Close(); return s; @@ -40,8 +41,24 @@ function write(filename, contents) { function replaceInFile(filename, regexp, replacement) { write(filename, read(filename).replace(regexp, replacement)); } -function exec(s) { +function exec(s, output) { + WScript.Echo('Executing ' + s); var o=shell.Exec(s); + while (o.Status == 0) { + WScript.Sleep(100); + } + WScript.Echo("Command exited with code " + o.Status); +} + +function fork(s) { + WScript.Echo('Executing ' + s); + var o=shell.Exec(s); + while (o.Status != 1) { + WScript.Sleep(100); + } + WScript.Echo(o.StdOut.ReadAll()); + WScript.Echo(o.StdErr.ReadAll()); + WScript.Echo("Command exited with code " + o.Status); } var args = WScript.Arguments, PROJECT_PATH="example", @@ -61,6 +78,14 @@ var MANIFEST_PATH=PROJECT_PATH+'\\AndroidManifest.xml'; var TARGET=shell.Exec('android.bat list targets').StdOut.ReadAll().match(/id:\s([0-9]).*/)[1]; var VERSION=read('VERSION').replace(/\r\n/,'').replace(/\n/,''); +WScript.Echo("Project path: " + PROJECT_PATH); +WScript.Echo("Package: " + PACKAGE); +WScript.Echo("Activity: " + ACTIVITY); +WScript.Echo("Package as path: " + PACKAGE_AS_PATH); +WScript.Echo("Activity path: " + ACTIVITY_PATH); +WScript.Echo("Manifest path: " + MANIFEST_PATH); +WScript.Echo("Cordova version: " + VERSION); + // clobber any existing example /* @@ -84,24 +109,26 @@ exec('ant.bat -f framework\\build.xml jar'); // copy in the project template exec('cmd /c xcopy bin\\templates\\project '+PROJECT_PATH+' /S /Y'); +// copy example www assets +exec('cmd /c xcopy ' + PROJECT_PATH + '\\cordova\\assets ' + PROJECT_PATH + ' /S /Y'); + // copy in cordova.js -exec('cmd /c copy framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y'); +exec('cmd /c copy framework\\assets\\js\\cordova.android.js '+PROJECT_PATH+'\\.cordova\\android\\cordova-'+VERSION+'.js /Y'); // copy in cordova.jar -exec('cmd /c copy framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y'); +exec('cmd /c copy framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\.cordova\\android\\cordova-'+VERSION+'.jar /Y'); -// copy in default activity -exec('cmd /c copy bin\\templates\\Activity.java '+ACTIVITY_PATH+' /Y'); +// copy in xml +exec('cmd /c copy framework\\res\\xml\\cordova.xml ' + PROJECT_PATH + '\\.cordova\\android\\cordova.xml /Y'); +exec('cmd /c copy framework\\res\\xml\\plugins.xml ' + PROJECT_PATH + '\\.cordova\\android\\plugins.xml /Y'); -// interpolate the activity name and package -replaceInFile(ACTIVITY_PATH, /__ACTIVITY__/, ACTIVITY); -replaceInFile(ACTIVITY_PATH, /__ID__/, PACKAGE); +// write out config file +write(PROJECT_PATH + '\\.cordova\\config', + 'VERSION=' + VERSION + '\r\n' + + 'PROJECT_PATH=' + PROJECT_PATH + '\r\n' + + 'PACKAGE=' + PACKAGE + '\r\n' + + 'ACTIVITY=' + ACTIVITY + '\r\n' + + 'TARGET=' + TARGET); -replaceInFile(MANIFEST_PATH, /__ACTIVITY__/, ACTIVITY); -replaceInFile(MANIFEST_PATH, /__PACKAGE__/, PACKAGE); - -/* -# leave the id for launching -touch $PROJECT_PATH/package-activity -echo $PACKAGE/$PACKAGE.$ACTIVITY > $PROJECT_PATH/package-activity -*/ +// run project-specific create process +fork('cscript.exe ' + PROJECT_PATH + '\\cordova\\create.js'); \ No newline at end of file diff --git a/bin/templates/project/cordova/create.bat b/bin/templates/project/cordova/create.bat new file mode 100644 index 00000000..cfde65a5 --- /dev/null +++ b/bin/templates/project/cordova/create.bat @@ -0,0 +1,2 @@ +echo "BALLS" +cscript cordova\create.js \ No newline at end of file diff --git a/bin/templates/project/cordova/create.js b/bin/templates/project/cordova/create.js new file mode 100644 index 00000000..b87f0a10 --- /dev/null +++ b/bin/templates/project/cordova/create.js @@ -0,0 +1,70 @@ +var shell=WScript.CreateObject("WScript.Shell"); + +function exec(s, output) { + WScript.Echo('Executing ' + s); + var o=shell.Exec(s); + while (o.Status == 0) { + WScript.Sleep(100); + } + WScript.Echo("Command exited with code " + o.Status); +} +function read(filename) { + WScript.Echo('Reading in ' + filename); + var fso=WScript.CreateObject("Scripting.FileSystemObject"); + var f=fso.OpenTextFile(filename, 1); + var s=f.ReadAll(); + f.Close(); + return s; +} +function write(filename, contents) { + var fso=WScript.CreateObject("Scripting.FileSystemObject"); + var f=fso.OpenTextFile(filename, 2, true); + f.Write(contents); + f.Close(); +} +function replaceInFile(filename, regexp, replacement) { + write(filename, read(filename).replace(regexp, replacement)); +} + +// working dir +var PWD = WScript.ScriptFullName.split('\\cordova\\create.js').join(''); + +var fso=WScript.CreateObject("Scripting.FileSystemObject"); +var f=fso.OpenTextFile(PWD + '\\.cordova\\config', 1); +while (!f.AtEndOfStream) { + var prop = f.ReadLine().split('='); + var line = 'var ' + prop[0] + '=' + "'" + prop[1] + "';"; + eval(line); // hacky shit to load config but whatevs +} + +var PACKAGE_AS_PATH=PACKAGE.replace(/\./g, '\\'); +var ACTIVITY_PATH=PWD+'\\src\\'+PACKAGE_AS_PATH+'\\'+ACTIVITY+'.java'; +var MANIFEST_PATH=PWD+'\\AndroidManifest.xml'; + +exec('android.bat create project --target ' + TARGET + ' --path ' + PWD + ' --package ' + PACKAGE + ' --activity ' + ACTIVITY); + +// copy in activity and other android assets +exec('cmd /c xcopy ' + PWD + '\\cordova\\templates\project\* ' + PWD +' /Y /S'); + +// copy in cordova.js +exec('cmd /c copy ' + PWD + '\\.cordova\\android\\cordova-' + VERSION + '.js ' + PWD + '\\assets\\www /Y'); + +// copy in cordova.jar +exec('cmd /c copy ' + PWD + '\\.cordova\\android\\cordova-' + VERSION + '.jar ' + PWD + '\\libs /Y'); + +// copy in res/xml +exec('cmd /c md ' + PWD + '\\res\\xml'); +exec('cmd /c copy ' + PWD + '\\.cordova\\android\\cordova.xml ' + PWD + '\\res\\xml /Y'); +exec('cmd /c copy ' + PWD + '\\.cordova\\android\\plugins.xml ' + PWD + '\\res\\xml /Y'); + +// copy in default activity +exec('cmd /c copy ' + PWD + '\\cordova\\templates\\Activity.java ' + ACTIVITY_PATH + ' /Y'); + +// interpolate the activity name and package +replaceInFile(ACTIVITY_PATH, /__ACTIVITY__/, ACTIVITY); +replaceInFile(ACTIVITY_PATH, /__ID__/, PACKAGE); + +replaceInFile(MANIFEST_PATH, /__ACTIVITY__/, ACTIVITY); +replaceInFile(MANIFEST_PATH, /__PACKAGE__/, PACKAGE); + +WScript.Echo('DONE!'); \ No newline at end of file diff --git a/bin/templates/project/cordova/debug.bat b/bin/templates/project/cordova/debug.bat new file mode 100644 index 00000000..e69de29b diff --git a/bin/templates/project/cordova/emulate.bat b/bin/templates/project/cordova/emulate.bat new file mode 100644 index 00000000..e69de29b diff --git a/bin/templates/project/cordova/log.bat b/bin/templates/project/cordova/log.bat new file mode 100644 index 00000000..e69de29b From 1f45503e2f313e9d13aefc73660c225c0edde3ff Mon Sep 17 00:00:00 2001 From: filmaj <maj.fil@gmail.com> Date: Thu, 17 May 2012 10:59:38 -0700 Subject: [PATCH 02/13] [CB-659] create script for android on windows now works fully. also pulls down commons-codec jar appropriately --- bin/create.js | 44 +++++++++++++++++++++++-- bin/templates/project/cordova/create.js | 5 ++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/bin/create.js b/bin/create.js index 96e61eca..a15db7a2 100644 --- a/bin/create.js +++ b/bin/create.js @@ -64,6 +64,9 @@ function fork(s) { var args = WScript.Arguments, PROJECT_PATH="example", PACKAGE="org.apache.cordova.example", ACTIVITY="cordovaExample", shell=WScript.CreateObject("WScript.Shell"); + +// working dir +var ROOT = WScript.ScriptFullName.split('\\bin\\create.js').join(''); if (args.Count() == 3) { WScript.Echo('Found expected arguments'); @@ -86,7 +89,7 @@ WScript.Echo("Activity path: " + ACTIVITY_PATH); WScript.Echo("Manifest path: " + MANIFEST_PATH); WScript.Echo("Cordova version: " + VERSION); -// clobber any existing example +// TODO: clobber any existing example /* if [ $# -eq 0 ] @@ -101,13 +104,50 @@ exec('android.bat create project --target '+TARGET+' --path '+PROJECT_PATH+' --p // update the cordova framework project to a target that exists on this machine exec('android.bat update project --target '+TARGET+' --path framework'); +// pull down commons codec if necessary +var fso = WScript.CreateObject('Scripting.FileSystemObject'); +if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.6.jar')) { + // We need the .jar + var url = 'http://mirror.symnds.com/software/Apache//commons/codec/binaries/commons-codec-1.6-bin.zip'; + var savePath = ROOT + '\\framework\\libs\\commons-codec-1.6-bin.zip'; + if (!fso.FileExists(savePath)) { + // We need the zip to get the jar + var xhr = WScript.CreateObject('MSXML2.XMLHTTP'); + xhr.open('GET', url, false); + xhr.send(); + if (xhr.status == 200) { + var stream = WScript.CreateObject('ADODB.Stream'); + stream.Open(); + stream.Type = 1; + stream.Write(xhr.ResponseBody); + stream.Position = 0; + stream.SaveToFile(savePath); + stream.Close(); + } else { + WScript.Echo('Could not retrieve the commons-codec. Please download it yourself and put into the framework/libs directory. This process may fail now. Sorry.'); + } + } + var app = WScript.CreateObject('Shell.Application'); + var source = app.NameSpace(savePath).Items(); + var target = app.NameSpace(ROOT + '\\framework\\libs'); + target.CopyHere(source, 256); + + // Move the jar into libs + fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.6\\commons-codec-1.6.jar', ROOT + '\\framework\\libs\\commons-codec-1.6.jar'); + + // Clean up + fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.6-bin.zip'); + fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.6', true); +} + + // compile cordova.js and cordova.jar // if you see an error about "Unable to resolve target" then you may need to // update your android tools or install an additional Android platform version exec('ant.bat -f framework\\build.xml jar'); // copy in the project template -exec('cmd /c xcopy bin\\templates\\project '+PROJECT_PATH+' /S /Y'); +exec('cmd /c xcopy bin\\templates\\project\\* '+PROJECT_PATH+' /S /Y'); // copy example www assets exec('cmd /c xcopy ' + PROJECT_PATH + '\\cordova\\assets ' + PROJECT_PATH + ' /S /Y'); diff --git a/bin/templates/project/cordova/create.js b/bin/templates/project/cordova/create.js index b87f0a10..2bf56031 100644 --- a/bin/templates/project/cordova/create.js +++ b/bin/templates/project/cordova/create.js @@ -9,7 +9,6 @@ function exec(s, output) { WScript.Echo("Command exited with code " + o.Status); } function read(filename) { - WScript.Echo('Reading in ' + filename); var fso=WScript.CreateObject("Scripting.FileSystemObject"); var f=fso.OpenTextFile(filename, 1); var s=f.ReadAll(); @@ -44,7 +43,7 @@ var MANIFEST_PATH=PWD+'\\AndroidManifest.xml'; exec('android.bat create project --target ' + TARGET + ' --path ' + PWD + ' --package ' + PACKAGE + ' --activity ' + ACTIVITY); // copy in activity and other android assets -exec('cmd /c xcopy ' + PWD + '\\cordova\\templates\project\* ' + PWD +' /Y /S'); +exec('cmd /c xcopy ' + PWD + '\\cordova\\templates\\project\\* ' + PWD +' /Y /S'); // copy in cordova.js exec('cmd /c copy ' + PWD + '\\.cordova\\android\\cordova-' + VERSION + '.js ' + PWD + '\\assets\\www /Y'); @@ -67,4 +66,4 @@ replaceInFile(ACTIVITY_PATH, /__ID__/, PACKAGE); replaceInFile(MANIFEST_PATH, /__ACTIVITY__/, ACTIVITY); replaceInFile(MANIFEST_PATH, /__PACKAGE__/, PACKAGE); -WScript.Echo('DONE!'); \ No newline at end of file +WScript.Echo('Create completed successfully.'); \ No newline at end of file From 0850229c9ff9e81021141ae8edcd0ccd655ffe74 Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Thu, 17 May 2012 11:14:23 -0700 Subject: [PATCH 03/13] [CB-804] ADded proper cordova icon sizes for the create script --- .../project/res/drawable-hdpi/icon.png | Bin 0 -> 6080 bytes .../project/res/drawable-ldpi/icon.png | Bin 0 -> 3096 bytes .../project/res/drawable-mdpi/icon.png | Bin 0 -> 4090 bytes .../project/res/drawable-xhdpi/icon.png | Bin 0 -> 7685 bytes .../templates/project/res/drawable/icon.png | Bin 5800 -> 7685 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 bin/templates/project/cordova/templates/project/res/drawable-hdpi/icon.png create mode 100644 bin/templates/project/cordova/templates/project/res/drawable-ldpi/icon.png create mode 100644 bin/templates/project/cordova/templates/project/res/drawable-mdpi/icon.png create mode 100644 bin/templates/project/cordova/templates/project/res/drawable-xhdpi/icon.png mode change 100755 => 100644 bin/templates/project/cordova/templates/project/res/drawable/icon.png diff --git a/bin/templates/project/cordova/templates/project/res/drawable-hdpi/icon.png b/bin/templates/project/cordova/templates/project/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4d27634485b3a20d8a0188107e84812f4f71084b GIT binary patch literal 6080 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 literal 0 HcmV?d00001 diff --git a/bin/templates/project/cordova/templates/project/res/drawable-ldpi/icon.png b/bin/templates/project/cordova/templates/project/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd5032a4f2e0b734d4bdece13564c4b55de963a6 GIT binary patch literal 3096 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 NTPp`rk)>zke*nlqMCSki literal 0 HcmV?d00001 diff --git a/bin/templates/project/cordova/templates/project/res/drawable-mdpi/icon.png b/bin/templates/project/cordova/templates/project/res/drawable-mdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e79c6062c1dcd9e98f092dbd7e51b5b76dd897d2 GIT binary patch literal 4090 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 literal 0 HcmV?d00001 diff --git a/bin/templates/project/cordova/templates/project/res/drawable-xhdpi/icon.png b/bin/templates/project/cordova/templates/project/res/drawable-xhdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ec7ffbfbdafc58df28e6e89041ee21acd0f6177e GIT binary patch literal 7685 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 literal 0 HcmV?d00001 diff --git a/bin/templates/project/cordova/templates/project/res/drawable/icon.png b/bin/templates/project/cordova/templates/project/res/drawable/icon.png old mode 100755 new mode 100644 index 697df7f37de3bc418dcee9409240eab4891e496d..ec7ffbfbdafc58df28e6e89041ee21acd0f6177e GIT binary patch literal 7685 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 literal 5800 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 From 5dacb8d2d5bb1e8a9806f07185f510a608586f15 Mon Sep 17 00:00:00 2001 From: Don Coleman <dcoleman@chariotsolutions.com> Date: Thu, 17 May 2012 14:51:41 -0400 Subject: [PATCH 04/13] [CB-792] Add HTTP status code to FileTransferError --- .../src/org/apache/cordova/FileTransfer.java | 553 ++++++++++-------- 1 file changed, 305 insertions(+), 248 deletions(-) diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index a631a055..368d97ad 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -27,6 +27,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -35,7 +36,6 @@ import java.util.Iterator; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; @@ -57,7 +57,7 @@ public class FileTransfer extends Plugin { private static final String LOG_TAG = "FileTransfer"; private static final String LINE_START = "--"; private static final String LINE_END = "\r\n"; - private static final String BOUNDRY = "*****"; + private static final String BOUNDARY = "*****"; public static int FILE_NOT_FOUND_ERR = 1; public static int INVALID_URL_ERR = 2; @@ -82,49 +82,251 @@ public class FileTransfer extends Plugin { return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target"); } - try { - if (action.equals("upload")) { - // Setup the options - String fileKey = null; - String fileName = null; - String mimeType = null; + if (action.equals("upload")) { + return upload(source, target, args); + } else if (action.equals("download")) { + return download(source, target); + } else { + return new PluginResult(PluginResult.Status.INVALID_ACTION); + } + } - fileKey = getArgument(args, 2, "file"); - fileName = getArgument(args, 3, "image.jpg"); - mimeType = getArgument(args, 4, "image/jpeg"); - JSONObject params = args.optJSONObject(5); - boolean trustEveryone = args.optBoolean(6); - boolean chunkedMode = args.optBoolean(7) || args.isNull(7); //Always use chunked mode unless set to false as per API - FileUploadResult r = upload(source, target, fileKey, fileName, mimeType, params, trustEveryone, chunkedMode); - Log.d(LOG_TAG, "****** About to return a result from upload"); - return new PluginResult(PluginResult.Status.OK, r.toJSONObject()); - } else if (action.equals("download")) { - JSONObject r = download(source, target); - Log.d(LOG_TAG, "****** About to return a result from download"); - return new PluginResult(PluginResult.Status.OK, r); - } else { - return new PluginResult(PluginResult.Status.INVALID_ACTION); + /** + * Uploads the specified file to the server URL provided using an HTTP multipart request. + * @param source Full path of the file on the file system + * @param target URL of the server to receive the file + * @param args JSON Array of args + * + * args[2] fileKey Name of file request parameter + * args[3] fileName File name to be used on server + * args[4] mimeType Describes file content type + * args[5] params key:value pairs of user-defined parameters + * @return FileUploadResult containing result of upload request + */ + private PluginResult upload(String source, String target, JSONArray args) { + Log.d(LOG_TAG, "upload " + source + " to " + target); + + HttpURLConnection conn = null; + try { + // Setup the options + String fileKey = getArgument(args, 2, "file"); + String fileName = getArgument(args, 3, "image.jpg"); + String mimeType = getArgument(args, 4, "image/jpeg"); + JSONObject params = args.optJSONObject(5); + if (params == null) params = new JSONObject(); + boolean trustEveryone = args.optBoolean(6); + boolean chunkedMode = args.optBoolean(7) || args.isNull(7); //Always use chunked mode unless set to false as per API + + Log.d(LOG_TAG, "fileKey: " + fileKey); + Log.d(LOG_TAG, "fileName: " + fileName); + Log.d(LOG_TAG, "mimeType: " + mimeType); + Log.d(LOG_TAG, "params: " + params); + Log.d(LOG_TAG, "trustEveryone: " + trustEveryone); + Log.d(LOG_TAG, "chunkedMode: " + chunkedMode); + + // Create return object + FileUploadResult result = new FileUploadResult(); + + // Get a input stream of the file on the phone + FileInputStream fileInputStream = (FileInputStream) getPathFromUri(source); + + DataOutputStream dos = null; + + int bytesRead, bytesAvailable, bufferSize; + long totalBytes; + byte[] buffer; + int maxBufferSize = 8096; + + //------------------ CLIENT REQUEST + // open a URL connection to the server + URL url = new URL(target); + + // Open a HTTP connection to the URL based on protocol + if (url.getProtocol().toLowerCase().equals("https")) { + // Using standard HTTPS connection. Will not allow self signed certificate + if (!trustEveryone) { + conn = (HttpsURLConnection) url.openConnection(); + } + // Use our HTTPS connection that blindly trusts everyone. + // This should only be used in debug environments + else { + // Setup the HTTPS connection class to trust everyone + trustAllHosts(); + HttpsURLConnection https = (HttpsURLConnection) url.openConnection(); + // Save the current hostnameVerifier + defaultHostnameVerifier = https.getHostnameVerifier(); + // Setup the connection not to verify hostnames + https.setHostnameVerifier(DO_NOT_VERIFY); + conn = https; + } } + // Return a standard HTTP connection + else { + conn = (HttpURLConnection) url.openConnection(); + } + + // Allow Inputs + conn.setDoInput(true); + + // Allow Outputs + conn.setDoOutput(true); + + // Don't use a cached copy. + conn.setUseCaches(false); + + // Use a post method. + conn.setRequestMethod("POST"); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY); + + // Handle the other headers + try { + JSONObject headers = params.getJSONObject("headers"); + for (Iterator iter = headers.keys(); iter.hasNext();) + { + String headerKey = iter.next().toString(); + conn.setRequestProperty(headerKey, headers.getString(headerKey)); + } + } catch (JSONException e1) { + // No headers to be manipulated! + } + + // Set the cookies on the response + String cookie = CookieManager.getInstance().getCookie(target); + if (cookie != null) { + conn.setRequestProperty("Cookie", cookie); + } + + + /* + * Store the non-file portions of the multipart data as a string, so that we can add it + * to the contentSize, since it is part of the body of the HTTP request. + */ + String extraParams = ""; + try { + for (Iterator iter = params.keys(); iter.hasNext();) { + Object key = iter.next(); + if(!String.valueOf(key).equals("headers")) + { + extraParams += LINE_START + BOUNDARY + LINE_END; + extraParams += "Content-Disposition: form-data; name=\"" + key.toString() + "\";"; + extraParams += LINE_END + LINE_END; + extraParams += params.getString(key.toString()); + extraParams += LINE_END; + } + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + + extraParams += LINE_START + BOUNDARY + LINE_END; + extraParams += "Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\""; + + String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END; + String tailParams = LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END; + + // Should set this up as an option + if (chunkedMode) { + conn.setChunkedStreamingMode(maxBufferSize); + } + else + { + int stringLength = extraParams.length() + midParams.length() + tailParams.length() + fileName.getBytes("UTF-8").length; + Log.d(LOG_TAG, "String Length: " + stringLength); + int fixedLength = (int) fileInputStream.getChannel().size() + stringLength; + Log.d(LOG_TAG, "Content Length: " + fixedLength); + conn.setFixedLengthStreamingMode(fixedLength); + } + + + dos = new DataOutputStream( conn.getOutputStream() ); + dos.writeBytes(extraParams); + //We don't want to chagne encoding, we just want this to write for all Unicode. + dos.write(fileName.getBytes("UTF-8")); + dos.writeBytes(midParams); + + // create a buffer of maximum size + bytesAvailable = fileInputStream.available(); + bufferSize = Math.min(bytesAvailable, maxBufferSize); + buffer = new byte[bufferSize]; + + // read file and write it into form... + bytesRead = fileInputStream.read(buffer, 0, bufferSize); + totalBytes = 0; + + while (bytesRead > 0) { + totalBytes += bytesRead; + result.setBytesSent(totalBytes); + dos.write(buffer, 0, bufferSize); + bytesAvailable = fileInputStream.available(); + bufferSize = Math.min(bytesAvailable, maxBufferSize); + bytesRead = fileInputStream.read(buffer, 0, bufferSize); + } + + // send multipart form data necesssary after file data... + dos.writeBytes(tailParams); + + // close streams + fileInputStream.close(); + dos.flush(); + dos.close(); + + //------------------ read the SERVER RESPONSE + StringBuffer responseString = new StringBuffer(""); + DataInputStream inStream; + 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; + while (( line = inStream.readLine()) != null) { + responseString.append(line); + } + Log.d(LOG_TAG, "got response from server"); + Log.d(LOG_TAG, responseString.toString()); + + // send request and retrieve response + result.setResponseCode(conn.getResponseCode()); + result.setResponse(responseString.toString()); + + inStream.close(); + + // Revert back to the proper verifier and socket factories + if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) { + ((HttpsURLConnection) conn).setHostnameVerifier(defaultHostnameVerifier); + HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); + } + + Log.d(LOG_TAG, "****** About to return a result from upload"); + return new PluginResult(PluginResult.Status.OK, result.toJSONObject()); + } catch (FileNotFoundException e) { - Log.e(LOG_TAG, e.getMessage(), e); - JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target); + JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn); + Log.e(LOG_TAG, error.toString(), e); return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, e.getMessage(), e); - JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target); - return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } catch (SSLException e) { - Log.e(LOG_TAG, e.getMessage(), e); - Log.d(LOG_TAG, "Got my ssl exception!!!"); - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target); + } catch (MalformedURLException e) { + JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, conn); + Log.e(LOG_TAG, error.toString(), e); return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); } catch (IOException e) { - Log.e(LOG_TAG, e.getMessage(), e); - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target); + JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); + Log.e(LOG_TAG, error.toString(), e); return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); } catch (JSONException e) { Log.e(LOG_TAG, e.getMessage(), e); return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } catch (Throwable t) { + // Shouldn't happen, but will + JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); + Log.wtf(LOG_TAG, error.toString(), t); + return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); + } finally { + if (conn != null) { + conn.disconnect(); + } } } @@ -172,18 +374,36 @@ public class FileTransfer extends Plugin { } } - /** - * Create an error object based on the passed in errorCode - * @param errorCode the error - * @return JSONObject containing the error - */ - private JSONObject createFileTransferError(int errorCode, String source, String target) { + private JSONObject createFileTransferError(int errorCode, String source, String target, HttpURLConnection connection) { + + Integer httpStatus = null; + + if (connection != null) { + try { + httpStatus = connection.getResponseCode(); + } catch (IOException e) { + Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e); + } + } + + return createFileTransferError(errorCode, source, target, httpStatus); + } + + /** + * Create an error object based on the passed in errorCode + * @param errorCode the error + * @return JSONObject containing the error + */ + private JSONObject createFileTransferError(int errorCode, String source, String target, Integer httpStatus) { JSONObject error = null; try { error = new JSONObject(); error.put("code", errorCode); error.put("source", source); error.put("target", target); + if (httpStatus != null) { + error.put("http_status", httpStatus); + } } catch (JSONException e) { Log.e(LOG_TAG, e.getMessage(), e); } @@ -208,198 +428,6 @@ public class FileTransfer extends Plugin { return arg; } - /** - * Uploads the specified file to the server URL provided using an HTTP - * multipart request. - * @param file Full path of the file on the file system - * @param server URL of the server to receive the file - * @param fileKey Name of file request parameter - * @param fileName File name to be used on server - * @param mimeType Describes file content type - * @param params key:value pairs of user-defined parameters - * @return FileUploadResult containing result of upload request - */ - public FileUploadResult upload(String file, String server, final String fileKey, final String fileName, - final String mimeType, JSONObject params, boolean trustEveryone, boolean chunkedMode) throws IOException, SSLException { - // Create return object - FileUploadResult result = new FileUploadResult(); - - // Get a input stream of the file on the phone - FileInputStream fileInputStream = (FileInputStream) getPathFromUri(file); - - HttpURLConnection conn = null; - DataOutputStream dos = null; - - int bytesRead, bytesAvailable, bufferSize; - long totalBytes; - byte[] buffer; - int maxBufferSize = 8096; - - //------------------ CLIENT REQUEST - // open a URL connection to the server - URL url = new URL(server); - - // Open a HTTP connection to the URL based on protocol - if (url.getProtocol().toLowerCase().equals("https")) { - // Using standard HTTPS connection. Will not allow self signed certificate - if (!trustEveryone) { - conn = (HttpsURLConnection) url.openConnection(); - } - // Use our HTTPS connection that blindly trusts everyone. - // This should only be used in debug environments - else { - // Setup the HTTPS connection class to trust everyone - trustAllHosts(); - HttpsURLConnection https = (HttpsURLConnection) url.openConnection(); - // Save the current hostnameVerifier - defaultHostnameVerifier = https.getHostnameVerifier(); - // Setup the connection not to verify hostnames - https.setHostnameVerifier(DO_NOT_VERIFY); - conn = https; - } - } - // Return a standard HTTP connection - else { - conn = (HttpURLConnection) url.openConnection(); - } - - // Allow Inputs - conn.setDoInput(true); - - // Allow Outputs - conn.setDoOutput(true); - - // Don't use a cached copy. - conn.setUseCaches(false); - - // Use a post method. - conn.setRequestMethod("POST"); - conn.setRequestProperty("Connection", "Keep-Alive"); - conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+BOUNDRY); - - // Handle the other headers - try { - JSONObject headers = params.getJSONObject("headers"); - for (Iterator iter = headers.keys(); iter.hasNext();) - { - String headerKey = iter.next().toString(); - conn.setRequestProperty(headerKey, headers.getString(headerKey)); - } - } catch (JSONException e1) { - // No headers to be manipulated! - } - - // Set the cookies on the response - String cookie = CookieManager.getInstance().getCookie(server); - if (cookie != null) { - conn.setRequestProperty("Cookie", cookie); - } - - - /* - * Store the non-file portions of the multipart data as a string, so that we can add it - * to the contentSize, since it is part of the body of the HTTP request. - */ - String extraParams = ""; - try { - for (Iterator iter = params.keys(); iter.hasNext();) { - Object key = iter.next(); - if(key.toString() != "headers") - { - extraParams += LINE_START + BOUNDRY + LINE_END; - extraParams += "Content-Disposition: form-data; name=\"" + key.toString() + "\";"; - extraParams += LINE_END + LINE_END; - extraParams += params.getString(key.toString()); - extraParams += LINE_END; - } - } - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - - extraParams += LINE_START + BOUNDRY + LINE_END; - extraParams += "Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\""; - - String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END; - String tailParams = LINE_END + LINE_START + BOUNDRY + LINE_START + LINE_END; - - // Should set this up as an option - if (chunkedMode) { - conn.setChunkedStreamingMode(maxBufferSize); - } - else - { - int stringLength = extraParams.length() + midParams.length() + tailParams.length() + fileName.getBytes("UTF-8").length; - Log.d(LOG_TAG, "String Length: " + stringLength); - int fixedLength = (int) fileInputStream.getChannel().size() + stringLength; - Log.d(LOG_TAG, "Content Length: " + fixedLength); - conn.setFixedLengthStreamingMode(fixedLength); - } - - - dos = new DataOutputStream( conn.getOutputStream() ); - dos.writeBytes(extraParams); - //We don't want to chagne encoding, we just want this to write for all Unicode. - dos.write(fileName.getBytes("UTF-8")); - dos.writeBytes(midParams); - - // create a buffer of maximum size - bytesAvailable = fileInputStream.available(); - bufferSize = Math.min(bytesAvailable, maxBufferSize); - buffer = new byte[bufferSize]; - - // read file and write it into form... - bytesRead = fileInputStream.read(buffer, 0, bufferSize); - totalBytes = 0; - - while (bytesRead > 0) { - totalBytes += bytesRead; - result.setBytesSent(totalBytes); - dos.write(buffer, 0, bufferSize); - bytesAvailable = fileInputStream.available(); - bufferSize = Math.min(bytesAvailable, maxBufferSize); - bytesRead = fileInputStream.read(buffer, 0, bufferSize); - } - - // send multipart form data necesssary after file data... - dos.writeBytes(tailParams); - - // close streams - fileInputStream.close(); - dos.flush(); - dos.close(); - - //------------------ read the SERVER RESPONSE - StringBuffer responseString = new StringBuffer(""); - DataInputStream inStream; - try { - inStream = new DataInputStream ( conn.getInputStream() ); - } catch(FileNotFoundException e) { - throw new IOException("Received error from server"); - } - - String line; - while (( line = inStream.readLine()) != null) { - responseString.append(line); - } - Log.d(LOG_TAG, "got response from server"); - Log.d(LOG_TAG, responseString.toString()); - - // send request and retrieve response - result.setResponseCode(conn.getResponseCode()); - result.setResponse(responseString.toString()); - - inStream.close(); - conn.disconnect(); - - // Revert back to the proper verifier and socket factories - if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) { - ((HttpsURLConnection)conn).setHostnameVerifier(defaultHostnameVerifier); - HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); - } - - return result; - } /** * Downloads a file form a given URL and saves it to the specified directory. @@ -408,7 +436,10 @@ public class FileTransfer extends Plugin { * @param target Full path of the file on the file system * @return JSONObject the downloaded file */ - public JSONObject download(String source, String target) throws IOException { + private PluginResult download(String source, String target) { + Log.d(LOG_TAG, "download " + source + " to " + target); + + HttpURLConnection connection = null; try { File file = getFileFromPath(target); @@ -419,7 +450,7 @@ public class FileTransfer extends Plugin { if(this.ctx.isUrlWhiteListed(source)) { URL url = new URL(source); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); //Add cookie support @@ -431,7 +462,7 @@ public class FileTransfer extends Plugin { connection.connect(); - Log.d(LOG_TAG, "Download file:" + url); + Log.d(LOG_TAG, "Download file: " + url); InputStream inputStream = connection.getInputStream(); byte[] buffer = new byte[1024]; @@ -450,23 +481,40 @@ public class FileTransfer extends Plugin { // create FileEntry object FileUtils fileUtil = new FileUtils(); + JSONObject fileEntry = fileUtil.getEntry(file); - return fileUtil.getEntry(file); + return new PluginResult(PluginResult.Status.OK, fileEntry); } else { - throw new IOException("Error: Unable to connect to domain"); + Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'"); + JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, 401); + return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); + } + + } catch (FileNotFoundException e) { + JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection); + Log.e(LOG_TAG, error.toString(), e); + return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); + } catch (MalformedURLException e) { + JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, connection); + Log.e(LOG_TAG, error.toString(), e); + return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); + } catch (Exception e) { // IOException, JSONException, NullPointer + JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection); + Log.e(LOG_TAG, error.toString(), e); + return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); + } finally { + if (connection != null) { + connection.disconnect(); } - } catch (Exception e) { - Log.d(LOG_TAG, e.getMessage(), e); - throw new IOException("Error while downloading"); } } /** * Get an input stream based on file path or content:// uri * - * @param path + * @param path foo * @return an input stream * @throws FileNotFoundException */ @@ -491,14 +539,23 @@ public class FileTransfer extends Plugin { /** * Get a File object from the passed in path * - * @param path - * @return + * @param path file path + * @return file object */ - private File getFileFromPath(String path) { - if (path.startsWith("file://")) { - return new File(path.substring(7)); + private File getFileFromPath(String path) throws FileNotFoundException { + File file; + String prefix = "file://"; + + if (path.startsWith(prefix)) { + file = new File(path.substring(prefix.length())); } else { - return new File(path); + file = new File(path); } + + if (file.getParent() == null) { + throw new FileNotFoundException(); + } + + return file; } } From 71e47aa77286298ddf03dc1fa8c9c42046877aad Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Mon, 14 May 2012 13:00:11 -0700 Subject: [PATCH 05/13] [CB-463] rewrite of accel plugin --- .../src/org/apache/cordova/AccelListener.java | 396 +++++++++--------- 1 file changed, 205 insertions(+), 191 deletions(-) diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index f751e4e2..40ddec49 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -18,7 +18,11 @@ */ package org.apache.cordova; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.Plugin; @@ -32,6 +36,8 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.location.Location; +import android.util.Log; import android.content.Context; /** @@ -40,20 +46,20 @@ import android.content.Context; */ public class AccelListener extends Plugin implements SensorEventListener { - public static int STOPPED = 0; - public static int STARTING = 1; + public static int STOPPED = 0; + public static int STARTING = 1; public static int RUNNING = 2; public static int ERROR_FAILED_TO_START = 3; - public float TIMEOUT = 30000; // Timeout in msec to shut off listener - - float x,y,z; // most recent acceleration values - long timestamp; // time of most recent value - int status; // status of listener - long lastAccessTime; // time the value was last retrieved + private float x,y,z; // most recent acceleration values + private long timestamp; // time of most recent value + private int status; // status of listener - private SensorManager sensorManager;// Sensor manager - Sensor mSensor; // Acceleration sensor returned by sensor manager + private SensorManager sensorManager; // Sensor manager + private Sensor mSensor; // Acceleration sensor returned by sensor manager + + private HashMap<String, String> watches = new HashMap<String, String>(); + private List<String> callbacks = new ArrayList<String>(); /** * Create an accelerometer listener. @@ -66,169 +72,149 @@ public class AccelListener extends Plugin implements SensorEventListener { this.setStatus(AccelListener.STOPPED); } - /** - * Sets the context of the Command. This can then be used to do things like - * get file paths associated with the Activity. - * - * @param ctx The context of the main Activity. - */ - public void setContext(CordovaInterface ctx) { - super.setContext(ctx); + /** + * Sets the context of the Command. This can then be used to do things like + * get file paths associated with the Activity. + * + * @param ctx The context of the main Activity. + */ + public void setContext(CordovaInterface ctx) { + super.setContext(ctx); this.sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE); - } + } - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackId The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. - */ - public PluginResult execute(String action, JSONArray args, String callbackId) { - PluginResult.Status status = PluginResult.Status.OK; - String result = ""; - - try { - if (action.equals("getStatus")) { - int i = this.getStatus(); - return new PluginResult(status, i); - } - else if (action.equals("start")) { - int i = this.start(); - return new PluginResult(status, i); - } - else if (action.equals("stop")) { - this.stop(); - return new PluginResult(status, 0); - } - else if (action.equals("getAcceleration")) { - // If not running, then this is an async call, so don't worry about waiting - if (this.status != AccelListener.RUNNING) { - int r = this.start(); - if (r == AccelListener.ERROR_FAILED_TO_START) { - return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START); - } - // Wait until running - long timeout = 2000; - while ((this.status == STARTING) && (timeout > 0)) { - timeout = timeout - 100; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - if (timeout == 0) { - return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START); - } - } - this.lastAccessTime = System.currentTimeMillis(); - JSONObject r = new JSONObject(); - r.put("x", this.x); - r.put("y", this.y); - r.put("z", this.z); - // TODO: Should timestamp be sent? - r.put("timestamp", this.timestamp); - return new PluginResult(status, r); - } - else if (action.equals("setTimeout")) { - try { - float timeout = Float.parseFloat(args.getString(0)); - this.setTimeout(timeout); - return new PluginResult(status, 0); - } catch (NumberFormatException e) { - status = PluginResult.Status.INVALID_ACTION; - e.printStackTrace(); - } catch (JSONException e) { - status = PluginResult.Status.JSON_EXCEPTION; - e.printStackTrace(); - } - } - else if (action.equals("getTimeout")) { - float f = this.getTimeout(); - return new PluginResult(status, f); - } else { - // Unsupported action - return new PluginResult(PluginResult.Status.INVALID_ACTION); - } - return new PluginResult(status, result); - } catch (JSONException e) { - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } - } + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackId The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.NO_RESULT; + String message = ""; + PluginResult result = new PluginResult(status, message); + result.setKeepCallback(true); + try { + if (action.equals("getAcceleration")) { + if (this.status != AccelListener.RUNNING) { + // If not running, then this is an async call, so don't worry about waiting + // We drop the callback onto our stack, call start, and let start and the sensor callback fire off the callback down the road + this.callbacks.add(callbackId); + this.start(); + } else { + return new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON()); + } + } + else if (action.equals("addWatch")) { + String watchId = args.getString(0); + this.watches.put(watchId, callbackId); + if (this.status != AccelListener.RUNNING) { + this.start(); + } + } + else if (action.equals("clearWatch")) { + String watchId = args.getString(0); + if (this.watches.containsKey(watchId)) { + this.watches.remove(watchId); + if (this.size() == 0) { + this.stop(); + } + } + return new PluginResult(PluginResult.Status.OK); + } else { + // Unsupported action + return new PluginResult(PluginResult.Status.INVALID_ACTION); + } + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + return result; + } - /** - * Identifies if action to be executed returns a value and should be run synchronously. - * - * @param action The action to execute - * @return T=returns value - */ - public boolean isSynch(String action) { - if (action.equals("getStatus")) { - return true; - } - else if (action.equals("getAcceleration")) { - // Can only return value if RUNNING - if (this.status == AccelListener.RUNNING) { - return true; - } - } - else if (action.equals("getTimeout")) { - return true; - } - return false; - } + /** + * Identifies if action to be executed returns a value and should be run synchronously. + * + * @param action The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + if (action.equals("getAcceleration") && this.status == AccelListener.RUNNING) { + return true; + } else if (action.equals("addWatch") && this.status == AccelListener.RUNNING) { + return true; + } else if (action.equals("clearWatch")) { + return true; + } + return false; + } /** * Called by AccelBroker when listener is to be shut down. * Stop listener. */ public void onDestroy() { - this.stop(); + this.stop(); } //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- + private int size() { + return this.watches.size() + this.callbacks.size(); + } /** * Start listening for acceleration sensor. * * @return status of listener */ - public int start() { + private int start() { + // If already starting or running, then just return + if ((this.status == AccelListener.RUNNING) || (this.status == AccelListener.STARTING)) { + return this.status; + } + + this.setStatus(AccelListener.STARTING); + + // Get accelerometer from sensor manager + List<Sensor> list = this.sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); - // If already starting or running, then just return - if ((this.status == AccelListener.RUNNING) || (this.status == AccelListener.STARTING)) { - return this.status; - } - - // Get accelerometer from sensor manager - List<Sensor> list = this.sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); - - // If found, then register as listener - if ((list != null) && (list.size() > 0)) { - this.mSensor = list.get(0); - this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_FASTEST); - this.setStatus(AccelListener.STARTING); - this.lastAccessTime = System.currentTimeMillis(); - } - - // If error, then set status to error - else { - this.setStatus(AccelListener.ERROR_FAILED_TO_START); - } - - return this.status; + // If found, then register as listener + if ((list != null) && (list.size() > 0)) { + this.mSensor = list.get(0); + this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_FASTEST); + this.setStatus(AccelListener.STARTING); + } else { + this.setStatus(AccelListener.ERROR_FAILED_TO_START); + this.fail(AccelListener.ERROR_FAILED_TO_START, "No sensors found to register accelerometer listening to."); + return this.status; + } + + // Wait until running + long timeout = 2000; + while ((this.status == STARTING) && (timeout > 0)) { + timeout = timeout - 100; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + if (timeout == 0) { + this.setStatus(AccelListener.ERROR_FAILED_TO_START); + this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started."); + } + return this.status; } /** * Stop listening to acceleration sensor. */ - public void stop() { + private void stop() { if (this.status != AccelListener.STOPPED) { - this.sensorManager.unregisterListener(this); + this.sensorManager.unregisterListener(this); } this.setStatus(AccelListener.STOPPED); } @@ -248,64 +234,92 @@ public class AccelListener extends Plugin implements SensorEventListener { * @param SensorEvent event */ public void onSensorChanged(SensorEvent event) { - - // Only look at accelerometer events + // Only look at accelerometer events if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { return; } // If not running, then just return if (this.status == AccelListener.STOPPED) { - return; + return; } + this.setStatus(AccelListener.RUNNING); + // Save time that event was received this.timestamp = System.currentTimeMillis(); this.x = event.values[0]; this.y = event.values[1]; - this.z = event.values[2]; + this.z = event.values[2]; - this.setStatus(AccelListener.RUNNING); - - // If values haven't been read for TIMEOUT time, then turn off accelerometer sensor to save power - if ((this.timestamp - this.lastAccessTime) > this.TIMEOUT) { - this.stop(); - } + this.win(); + + if (this.size() == 0) { + this.stop(); + } } - /** - * Get status of accelerometer sensor. - * - * @return status - */ - public int getStatus() { - return this.status; - } - - /** - * Set the timeout to turn off accelerometer sensor if getX() hasn't been called. - * - * @param timeout Timeout in msec. - */ - public void setTimeout(float timeout) { - this.TIMEOUT = timeout; - } - - /** - * Get the timeout to turn off accelerometer sensor if getX() hasn't been called. - * - * @return timeout in msec - */ - public float getTimeout() { - return this.TIMEOUT; - } - - /** - * Set the status and send it to JavaScript. - * @param status - */ - private void setStatus(int status) { - this.status = status; - } + private void fail(int code, String message) { + // Error object + JSONObject errorObj = new JSONObject(); + try { + errorObj.put("code", code); + errorObj.put("message", message); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + PluginResult err = new PluginResult(PluginResult.Status.ERROR, errorObj); + + for (String callbackId: this.callbacks) + { + this.error(err, callbackId); + } + this.callbacks.clear(); + + err.setKeepCallback(true); + + Iterator it = this.watches.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pairs = (Map.Entry)it.next(); + this.error(err, (String)pairs.getValue()); + } + } -} + private void win() { + // Success return object + PluginResult result = new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON()); + + for (String callbackId: this.callbacks) + { + this.success(result, callbackId); + } + this.callbacks.clear(); + + result.setKeepCallback(true); + + Iterator it = this.watches.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pairs = (Map.Entry)it.next(); + this.success(result, (String)pairs.getValue()); + } + } + + private void setStatus(int status) { + this.status = status; + } + + private JSONObject getAccelerationJSON() { + JSONObject r = new JSONObject(); + try { + r.put("x", this.x); + r.put("y", this.y); + r.put("z", this.z); + r.put("timestamp", this.timestamp); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return r; + } +} \ No newline at end of file From 24adc6d00cea0c603077bb3919a678071a0d32c6 Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Mon, 14 May 2012 13:04:07 -0700 Subject: [PATCH 06/13] [CB-463] added the JS updates for accel refactor --- framework/assets/js/cordova.android.js | 87 +++++++++++++------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js index 7bb06c2f..957be777 100644 --- a/framework/assets/js/cordova.android.js +++ b/framework/assets/js/cordova.android.js @@ -1,6 +1,6 @@ -// commit facaa38a0bd924aa15c14c372537c00382f1e593 +// commit 7b6ae77e5030060e8e99fe0b79ddcf9d698bf375 -// File generated at :: Thu May 10 2012 16:39:13 GMT-0700 (PDT) +// File generated at :: Mon May 14 2012 13:03:22 GMT-0700 (PDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -1144,13 +1144,14 @@ module.exports = { // file: lib/common/plugin/Acceleration.js define("cordova/plugin/Acceleration", function(require, exports, module) { var Acceleration = function(x, y, z, timestamp) { - this.x = x; - this.y = y; - this.z = z; - this.timestamp = timestamp || (new Date()).getTime(); + this.x = x; + this.y = y; + this.z = z; + this.timestamp = timestamp || (new Date()).getTime(); }; module.exports = Acceleration; + }); // file: lib/common/plugin/Camera.js @@ -3369,11 +3370,16 @@ define("cordova/plugin/accelerometer", function(require, exports, module) { * @constructor */ var utils = require("cordova/utils"), - exec = require("cordova/exec"); + exec = require("cordova/exec"), + Acceleration = require('cordova/plugin/Acceleration'); -// Local singleton variables. + +// Keeps reference to watchAcceleration calls. var timers = {}; +// Last returned acceleration object from native +var accel = null; + var accelerometer = { /** * Asynchronously aquires the current acceleration. @@ -3383,21 +3389,18 @@ var accelerometer = { * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) */ getCurrentAcceleration: function(successCallback, errorCallback, options) { - // successCallback required if (typeof successCallback !== "function") { - console.log("Accelerometer Error: successCallback is not a function"); - return; + throw "getCurrentAcceleration must be called with at least a success callback function as first parameter."; } - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Accelerometer Error: errorCallback is not a function"); - return; - } + var win = function(a) { + accel = new Acceleration(a.x, a.y, a.z, a.timestamp); + successCallback(accel); + }; // Get acceleration - exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); + exec(win, errorCallback, "Accelerometer", "getAcceleration", []); }, /** @@ -3409,36 +3412,34 @@ var accelerometer = { * @return String The watch id that must be passed to #clearWatch to stop watching. */ watchAcceleration: function(successCallback, errorCallback, options) { - // Default interval (10 sec) - var frequency = (options !== undefined && options.frequency !== undefined)? options.frequency : 10000; + var frequency = (options && options.frequency && typeof options.frequency == 'number') ? options.frequency : 10000; // successCallback required if (typeof successCallback !== "function") { - console.log("Accelerometer Error: successCallback is not a function"); - return; + throw "watchAcceleration must be called with at least a success callback function as first parameter."; } - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Accelerometer Error: errorCallback is not a function"); - return; - } - - // Make sure accelerometer timeout > frequency + 10 sec - exec( - function(timeout) { - if (timeout < (frequency + 10000)) { - exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); - } - }, - function(e) { }, "Accelerometer", "getTimeout", []); - - // Start watch timer + // Keep reference to watch id, and report accel readings as often as defined in frequency var id = utils.createUUID(); timers[id] = window.setInterval(function() { - exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); - }, (frequency ? frequency : 1)); + if (accel) { + successCallback(accel); + } + }, frequency); + + // Success callback from native just updates the accel object. + var win = function(a) { + accel = new Acceleration(a.x, a.y, a.z, a.timestamp); + }; + + // Fail callback clears the watch and sends an error back. + var fail = function(err) { + accelerometer.clearWatch(id); + errorCallback(err); + }; + + exec(win, fail, "Accelerometer", "addWatch", [id, frequency]); return id; }, @@ -3449,16 +3450,17 @@ var accelerometer = { * @param {String} id The id of the watch returned from #watchAcceleration. */ clearWatch: function(id) { - // Stop javascript timer & remove from timer list - if (id && timers[id] !== undefined) { + if (id && timers[id]) { window.clearInterval(timers[id]); delete timers[id]; + exec(null, null, "Accelerometer", "clearWatch", [id]); } } }; module.exports = accelerometer; + }); // file: lib/android/plugin/android/app.js @@ -5172,7 +5174,7 @@ window.cordova = require('cordova'); // Fire onDeviceReady event once all constructors have run and // cordova info has been received from native side. channel.join(function() { - channel.onDeviceReady.fire(); + require('cordova').fireDocumentEvent('deviceready'); }, channel.deviceReadyChannelsArray); }, [ channel.onDOMContentLoaded, channel.onNativeReady ]); @@ -5191,4 +5193,5 @@ window.cordova = require('cordova'); }(window)); + })(); \ No newline at end of file From cb98bbce1f9a99fa5e8af44886979f8614cb390f Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Mon, 14 May 2012 15:21:41 -0700 Subject: [PATCH 07/13] [CB-463] added accuracy checking to native accel implementation, this way getCurrentAcceleration returns fairly accurate results --- .../src/org/apache/cordova/AccelListener.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index 40ddec49..7596045c 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -54,6 +54,7 @@ public class AccelListener extends Plugin implements SensorEventListener { private float x,y,z; // most recent acceleration values private long timestamp; // time of most recent value private int status; // status of listener + private int accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE; private SensorManager sensorManager; // Sensor manager private Sensor mSensor; // Acceleration sensor returned by sensor manager @@ -184,7 +185,7 @@ public class AccelListener extends Plugin implements SensorEventListener { // If found, then register as listener if ((list != null) && (list.size() > 0)) { this.mSensor = list.get(0); - this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_FASTEST); + this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_UI); this.setStatus(AccelListener.STARTING); } else { this.setStatus(AccelListener.ERROR_FAILED_TO_START); @@ -217,6 +218,7 @@ public class AccelListener extends Plugin implements SensorEventListener { this.sensorManager.unregisterListener(this); } this.setStatus(AccelListener.STOPPED); + this.accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE; } /** @@ -226,6 +228,17 @@ public class AccelListener extends Plugin implements SensorEventListener { * @param accuracy */ public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Only look at accelerometer events + if (sensor.getType() != Sensor.TYPE_ACCELEROMETER) { + return; + } + + // If not running, then just return + if (this.status == AccelListener.STOPPED) { + return; + } + Log.d("ACCEL", "accuracy is now " + accuracy); + this.accuracy = accuracy; } /** @@ -246,16 +259,19 @@ public class AccelListener extends Plugin implements SensorEventListener { this.setStatus(AccelListener.RUNNING); - // Save time that event was received - this.timestamp = System.currentTimeMillis(); - this.x = event.values[0]; - this.y = event.values[1]; - this.z = event.values[2]; + if (this.accuracy >= SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM) { - this.win(); - - if (this.size() == 0) { - this.stop(); + // Save time that event was received + this.timestamp = System.currentTimeMillis(); + this.x = event.values[0]; + this.y = event.values[1]; + this.z = event.values[2]; + + this.win(); + + if (this.size() == 0) { + this.stop(); + } } } From df89d33fab102caed576ac01bf20c9b96ee30580 Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Mon, 14 May 2012 16:04:36 -0700 Subject: [PATCH 08/13] removed a trailing log --- framework/src/org/apache/cordova/AccelListener.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index 7596045c..bc8bf170 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -237,7 +237,6 @@ public class AccelListener extends Plugin implements SensorEventListener { if (this.status == AccelListener.STOPPED) { return; } - Log.d("ACCEL", "accuracy is now " + accuracy); this.accuracy = accuracy; } @@ -338,4 +337,4 @@ public class AccelListener extends Plugin implements SensorEventListener { } return r; } -} \ No newline at end of file +} From 531efe1e30f86d33a6a4980b03c68835f803c400 Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Mon, 14 May 2012 15:21:41 -0700 Subject: [PATCH 09/13] [CB-463] added accuracy checking to native accel implementation, this way getCurrentAcceleration returns fairly accurate results --- framework/src/org/apache/cordova/AccelListener.java | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index bc8bf170..6ccbecac 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -237,6 +237,7 @@ public class AccelListener extends Plugin implements SensorEventListener { if (this.status == AccelListener.STOPPED) { return; } + Log.d("ACCEL", "accuracy is now " + accuracy); this.accuracy = accuracy; } From 15ddef26f4e2966f951106d0c8881f1693f2ad5e Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Mon, 14 May 2012 16:04:36 -0700 Subject: [PATCH 10/13] removed a trailing log --- framework/src/org/apache/cordova/AccelListener.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index 6ccbecac..bc8bf170 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -237,7 +237,6 @@ public class AccelListener extends Plugin implements SensorEventListener { if (this.status == AccelListener.STOPPED) { return; } - Log.d("ACCEL", "accuracy is now " + accuracy); this.accuracy = accuracy; } From 2d5dcf24da18c210e08ccfcd473e1881a60bc429 Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Fri, 18 May 2012 13:50:45 -0700 Subject: [PATCH 11/13] [CB-463] updated js and rewrote accel plugin again to support the start/stop approach. optimized. single callback used for message passing --- framework/assets/js/cordova.android.js | 103 ++++++++--- .../src/org/apache/cordova/AccelListener.java | 167 ++++++------------ 2 files changed, 131 insertions(+), 139 deletions(-) diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js index 957be777..cdf619df 100644 --- a/framework/assets/js/cordova.android.js +++ b/framework/assets/js/cordova.android.js @@ -1,6 +1,6 @@ -// commit 7b6ae77e5030060e8e99fe0b79ddcf9d698bf375 +// commit 4a4ba9985c920850fe3f229abc60de984e196ab5 -// File generated at :: Mon May 14 2012 13:03:22 GMT-0700 (PDT) +// File generated at :: Fri May 18 2012 13:43:11 GMT-0700 (PDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -3373,13 +3373,57 @@ var utils = require("cordova/utils"), exec = require("cordova/exec"), Acceleration = require('cordova/plugin/Acceleration'); +// Is the accel sensor running? +var running = false; // Keeps reference to watchAcceleration calls. var timers = {}; +// Array of listeners; used to keep track of when we should call start and stop. +var listeners = []; + // Last returned acceleration object from native var accel = null; +// Tells native to start. +function start() { + exec(function(a) { + var tempListeners = listeners.slice(0); + accel = new Acceleration(a.x, a.y, a.z, a.timestamp); + for (var i = 0, l = tempListeners.length; i < l; i++) { + tempListeners[i].win(accel); + } + }, function(e) { + var tempListeners = listeners.slice(0); + for (var i = 0, l = tempListeners.length; i < l; i++) { + tempListeners[i].fail(e); + } + }, "Accelerometer", "start", []); + running = true; +} + +// Tells native to stop. +function stop() { + exec(null, null, "Accelerometer", "stop", []); + running = false; +} + +// Adds a callback pair to the listeners array +function createCallbackPair(win, fail) { + return {win:win, fail:fail}; +} + +// Removes a win/fail listener pair from the listeners array +function removeListeners(l) { + var idx = listeners.indexOf(l); + if (idx > -1) { + listeners.splice(idx, 1); + if (listeners.length === 0) { + stop(); + } + } +} + var accelerometer = { /** * Asynchronously aquires the current acceleration. @@ -3394,13 +3438,22 @@ var accelerometer = { throw "getCurrentAcceleration must be called with at least a success callback function as first parameter."; } + var p; var win = function(a) { - accel = new Acceleration(a.x, a.y, a.z, a.timestamp); - successCallback(accel); + successCallback(a); + removeListeners(p); + }; + var fail = function(e) { + errorCallback(e); + removeListeners(p); }; - // Get acceleration - exec(win, errorCallback, "Accelerometer", "getAcceleration", []); + p = createCallbackPair(win, fail); + listeners.push(p); + + if (!running) { + start(); + } }, /** @@ -3422,24 +3475,28 @@ var accelerometer = { // Keep reference to watch id, and report accel readings as often as defined in frequency var id = utils.createUUID(); - timers[id] = window.setInterval(function() { - if (accel) { - successCallback(accel); - } - }, frequency); - // Success callback from native just updates the accel object. - var win = function(a) { - accel = new Acceleration(a.x, a.y, a.z, a.timestamp); + var p = createCallbackPair(function(){}, function(e) { + errorCallback(e); + removeListeners(p); + }); + listeners.push(p); + + timers[id] = { + timer:window.setInterval(function() { + if (accel) { + successCallback(accel); + } + }, frequency), + listeners:p }; - // Fail callback clears the watch and sends an error back. - var fail = function(err) { - accelerometer.clearWatch(id); - errorCallback(err); - }; - - exec(win, fail, "Accelerometer", "addWatch", [id, frequency]); + if (running) { + // If we're already running then immediately invoke the success callback + successCallback(accel); + } else { + start(); + } return id; }, @@ -3452,9 +3509,9 @@ var accelerometer = { clearWatch: function(id) { // Stop javascript timer & remove from timer list if (id && timers[id]) { - window.clearInterval(timers[id]); + window.clearInterval(timers[id].timer); + removeListeners(timers[id].listeners); delete timers[id]; - exec(null, null, "Accelerometer", "clearWatch", [id]); } } }; diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index bc8bf170..551e8bcb 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -58,9 +58,8 @@ public class AccelListener extends Plugin implements SensorEventListener { private SensorManager sensorManager; // Sensor manager private Sensor mSensor; // Acceleration sensor returned by sensor manager - - private HashMap<String, String> watches = new HashMap<String, String>(); - private List<String> callbacks = new ArrayList<String>(); + + private String callbackId; // Keeps track of the single "start" callback ID passed in from JS /** * Create an accelerometer listener. @@ -93,62 +92,28 @@ public class AccelListener extends Plugin implements SensorEventListener { * @return A PluginResult object with a status and message. */ public PluginResult execute(String action, JSONArray args, String callbackId) { - PluginResult.Status status = PluginResult.Status.NO_RESULT; - String message = ""; - PluginResult result = new PluginResult(status, message); - result.setKeepCallback(true); - try { - if (action.equals("getAcceleration")) { - if (this.status != AccelListener.RUNNING) { - // If not running, then this is an async call, so don't worry about waiting - // We drop the callback onto our stack, call start, and let start and the sensor callback fire off the callback down the road - this.callbacks.add(callbackId); - this.start(); - } else { - return new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON()); - } - } - else if (action.equals("addWatch")) { - String watchId = args.getString(0); - this.watches.put(watchId, callbackId); - if (this.status != AccelListener.RUNNING) { - this.start(); - } - } - else if (action.equals("clearWatch")) { - String watchId = args.getString(0); - if (this.watches.containsKey(watchId)) { - this.watches.remove(watchId); - if (this.size() == 0) { - this.stop(); - } - } - return new PluginResult(PluginResult.Status.OK); - } else { - // Unsupported action - return new PluginResult(PluginResult.Status.INVALID_ACTION); - } - } catch (JSONException e) { - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } - return result; - } + PluginResult.Status status = PluginResult.Status.NO_RESULT; + String message = ""; + PluginResult result = new PluginResult(status, message); + result.setKeepCallback(true); - /** - * Identifies if action to be executed returns a value and should be run synchronously. - * - * @param action The action to execute - * @return T=returns value - */ - public boolean isSynch(String action) { - if (action.equals("getAcceleration") && this.status == AccelListener.RUNNING) { - return true; - } else if (action.equals("addWatch") && this.status == AccelListener.RUNNING) { - return true; - } else if (action.equals("clearWatch")) { - return true; + if (action.equals("start")) { + this.callbackId = callbackId; + if (this.status != AccelListener.RUNNING) { + // If not running, then this is an async call, so don't worry about waiting + // We drop the callback onto our stack, call start, and let start and the sensor callback fire off the callback down the road + this.start(); + } } - return false; + else if (action.equals("stop")) { + if (this.status == AccelListener.RUNNING) { + this.stop(); + } + } else { + // Unsupported action + return new PluginResult(PluginResult.Status.INVALID_ACTION); + } + return result; } /** @@ -162,10 +127,7 @@ public class AccelListener extends Plugin implements SensorEventListener { //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- - - private int size() { - return this.watches.size() + this.callbacks.size(); - } + // /** * Start listening for acceleration sensor. * @@ -267,74 +229,47 @@ public class AccelListener extends Plugin implements SensorEventListener { this.z = event.values[2]; this.win(); - - if (this.size() == 0) { - this.stop(); - } } } + // Sends an error back to JS private void fail(int code, String message) { - // Error object - JSONObject errorObj = new JSONObject(); - try { - errorObj.put("code", code); - errorObj.put("message", message); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - PluginResult err = new PluginResult(PluginResult.Status.ERROR, errorObj); - - for (String callbackId: this.callbacks) - { - this.error(err, callbackId); + // Error object + JSONObject errorObj = new JSONObject(); + try { + errorObj.put("code", code); + errorObj.put("message", message); + } catch (JSONException e) { + e.printStackTrace(); } - this.callbacks.clear(); - + PluginResult err = new PluginResult(PluginResult.Status.ERROR, errorObj); err.setKeepCallback(true); - - Iterator it = this.watches.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pairs = (Map.Entry)it.next(); - this.error(err, (String)pairs.getValue()); - } + + this.error(err, this.callbackId); } private void win() { - // Success return object - PluginResult result = new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON()); - - for (String callbackId: this.callbacks) - { - this.success(result, callbackId); - } - this.callbacks.clear(); - + // Success return object + PluginResult result = new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON()); result.setKeepCallback(true); - - Iterator it = this.watches.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pairs = (Map.Entry)it.next(); - this.success(result, (String)pairs.getValue()); - } + + this.success(result, this.callbackId); } - private void setStatus(int status) { + private void setStatus(int status) { this.status = status; } - private JSONObject getAccelerationJSON() { - JSONObject r = new JSONObject(); - try { - r.put("x", this.x); - r.put("y", this.y); - r.put("z", this.z); - r.put("timestamp", this.timestamp); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return r; - } + private JSONObject getAccelerationJSON() { + JSONObject r = new JSONObject(); + try { + r.put("x", this.x); + r.put("y", this.y); + r.put("z", this.z); + r.put("timestamp", this.timestamp); + } catch (JSONException e) { + e.printStackTrace(); + } + return r; + } } From 6b24f2d547695db060a860abe0cd1780abdbef5b Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Fri, 18 May 2012 15:23:57 -0700 Subject: [PATCH 12/13] Small spacing fixes --- .../src/org/apache/cordova/AccelListener.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index 551e8bcb..9e19d753 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -136,7 +136,7 @@ public class AccelListener extends Plugin implements SensorEventListener { private int start() { // If already starting or running, then just return if ((this.status == AccelListener.RUNNING) || (this.status == AccelListener.STARTING)) { - return this.status; + return this.status; } this.setStatus(AccelListener.STARTING); @@ -146,28 +146,28 @@ public class AccelListener extends Plugin implements SensorEventListener { // If found, then register as listener if ((list != null) && (list.size() > 0)) { - this.mSensor = list.get(0); - this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_UI); - this.setStatus(AccelListener.STARTING); + this.mSensor = list.get(0); + this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_UI); + this.setStatus(AccelListener.STARTING); } else { - this.setStatus(AccelListener.ERROR_FAILED_TO_START); - this.fail(AccelListener.ERROR_FAILED_TO_START, "No sensors found to register accelerometer listening to."); - return this.status; + this.setStatus(AccelListener.ERROR_FAILED_TO_START); + this.fail(AccelListener.ERROR_FAILED_TO_START, "No sensors found to register accelerometer listening to."); + return this.status; } // Wait until running long timeout = 2000; while ((this.status == STARTING) && (timeout > 0)) { - timeout = timeout - 100; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } + timeout = timeout - 100; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } } if (timeout == 0) { - this.setStatus(AccelListener.ERROR_FAILED_TO_START); - this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started."); + this.setStatus(AccelListener.ERROR_FAILED_TO_START); + this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started."); } return this.status; } @@ -222,13 +222,13 @@ public class AccelListener extends Plugin implements SensorEventListener { if (this.accuracy >= SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM) { - // Save time that event was received - this.timestamp = System.currentTimeMillis(); - this.x = event.values[0]; - this.y = event.values[1]; - this.z = event.values[2]; + // Save time that event was received + this.timestamp = System.currentTimeMillis(); + this.x = event.values[0]; + this.y = event.values[1]; + this.z = event.values[2]; - this.win(); + this.win(); } } From fae0c3dcfd5a73e5a4cbd47f218e52e03ad73eed Mon Sep 17 00:00:00 2001 From: macdonst <simon.macdonald@gmail.com> Date: Tue, 22 May 2012 14:48:02 -0400 Subject: [PATCH 13/13] Fix problem in Android template example getPicture --- .../project/cordova/templates/project/assets/www/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/templates/project/cordova/templates/project/assets/www/main.js b/bin/templates/project/cordova/templates/project/assets/www/main.js index 739a02e3..3a8b04a1 100644 --- a/bin/templates/project/cordova/templates/project/assets/www/main.js +++ b/bin/templates/project/cordova/templates/project/assets/www/main.js @@ -88,7 +88,7 @@ function dump_pic(data) { viewport.style.position = "absolute"; viewport.style.top = "10px"; viewport.style.left = "10px"; - document.getElementById("test_img").src = "data:image/jpeg;base64," + data; + document.getElementById("test_img").src = data; } function fail(msg) {