Merge branch 'master' of github.com:phonegap/phonegap-android

Conflicts:
	framework/res/xml/phonegap.xml
	framework/src/com/phonegap/DroidGap.java
This commit is contained in:
Anis Kadri
2011-10-14 15:43:13 -07:00
127 changed files with 14140 additions and 7433 deletions
+1 -1
View File
@@ -5,10 +5,10 @@
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:xlargeScreens="true"
android:resizeable="true"
android:anyDensity="true"
/>
<!-- android:xlargeScreens="true" screen supported only after Android-9 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
+18
View File
@@ -59,6 +59,14 @@ App.prototype.clearHistory = function() {
PhoneGap.exec(null, null, "App", "clearHistory", []);
};
/**
* Go to previous page displayed.
* This is the same as pressing the backbutton on Android device.
*/
App.prototype.backHistory = function() {
PhoneGap.exec(null, null, "App", "backHistory", []);
};
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
@@ -79,6 +87,16 @@ App.prototype.exitApp = function() {
return PhoneGap.exec(null, null, "App", "exitApp", []);
};
/**
* Add entry to approved list of URLs (whitelist) that will be loaded into PhoneGap container instead of default browser.
*
* @param origin URL regular expression to allow
* @param subdomains T=include all subdomains under origin
*/
App.prototype.addWhiteListEntry = function(origin, subdomains) {
return PhoneGap.exec(null, null, "App", "addWhiteListEntry", [origin, subdomains]);
};
PhoneGap.addConstructor(function() {
navigator.app = new App();
});
+46 -32
View File
@@ -49,6 +49,24 @@ Camera.EncodingType = {
};
Camera.prototype.EncodingType = Camera.EncodingType;
/**
* Type of pictures to select from. Only applicable when
* PictureSourceType is PHOTOLIBRARY or SAVEDPHOTOALBUM
*
* Example: navigator.camera.getPicture(success, fail,
* { quality: 80,
* destinationType: Camera.DestinationType.DATA_URL,
* sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
* mediaType: Camera.MediaType.PICTURE})
*/
Camera.MediaType = {
PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
VIDEO: 1, // allow selection of video only, ONLY RETURNS URL
ALLMEDIA : 2 // allow selection from all media types
};
Camera.prototype.MediaType = Camera.MediaType;
/**
* Source to getPicture from.
*
@@ -87,52 +105,48 @@ Camera.prototype.getPicture = function(successCallback, errorCallback, options)
console.log("Camera Error: errorCallback is not a function");
return;
}
this.options = options;
var quality = 80;
if (options.quality) {
quality = this.options.quality;
}
var maxResolution = 0;
if (options.maxResolution) {
maxResolution = this.options.maxResolution;
if (options === null || typeof options === "undefined") {
options = {};
}
var destinationType = Camera.DestinationType.DATA_URL;
if (this.options.destinationType) {
destinationType = this.options.destinationType;
if (options.quality === null || typeof options.quality === "undefined") {
options.quality = 80;
}
var sourceType = Camera.PictureSourceType.CAMERA;
if (typeof this.options.sourceType === "number") {
sourceType = this.options.sourceType;
if (options.maxResolution === null || typeof options.maxResolution === "undefined") {
options.maxResolution = 0;
}
var encodingType = Camera.EncodingType.JPEG;
if (typeof options.encodingType == "number") {
encodingType = this.options.encodingType;
if (options.destinationType === null || typeof options.destinationType === "undefined") {
options.destinationType = Camera.DestinationType.DATA_URL;
}
var targetWidth = -1;
if (typeof options.targetWidth == "number") {
targetWidth = options.targetWidth;
} else if (typeof options.targetWidth == "string") {
if (options.sourceType === null || typeof options.sourceType === "undefined") {
options.sourceType = Camera.PictureSourceType.CAMERA;
}
if (options.encodingType === null || typeof options.encodingType === "undefined") {
options.encodingType = Camera.EncodingType.JPEG;
}
if (options.mediaType === null || typeof options.mediaType === "undefined") {
options.mediaType = Camera.MediaType.PICTURE;
}
if (options.targetWidth === null || typeof options.targetWidth === "undefined") {
options.targetWidth = -1;
}
else if (typeof options.targetWidth == "string") {
var width = new Number(options.targetWidth);
if (isNaN(width) === false) {
targetWidth = width.valueOf();
options.targetWidth = width.valueOf();
}
}
var targetHeight = -1;
if (typeof options.targetHeight == "number") {
targetHeight = options.targetHeight;
} else if (typeof options.targetHeight == "string") {
if (options.targetHeight === null || typeof options.targetHeight === "undefined") {
options.targetHeight = -1;
}
else if (typeof options.targetHeight == "string") {
var height = new Number(options.targetHeight);
if (isNaN(height) === false) {
targetHeight = height.valueOf();
options.targetHeight = height.valueOf();
}
}
PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType]);
PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [options]);
};
PhoneGap.addConstructor(function() {
+23
View File
@@ -9,6 +9,21 @@
if (!PhoneGap.hasResource("compass")) {
PhoneGap.addResource("compass");
CompassError = function(){
this.code = null;
};
// Capture error codes
CompassError.COMPASS_INTERNAL_ERR = 0;
CompassError.COMPASS_NOT_SUPPORTED = 20;
CompassHeading = function() {
this.magneticHeading = null;
this.trueHeading = null;
this.headingAccuracy = null;
this.timestamp = null;
};
/**
* This class provides access to device Compass data.
* @constructor
@@ -111,6 +126,14 @@ Compass.prototype.clearWatch = function(id) {
}
};
Compass.prototype._castDate = function(pluginResult) {
if (pluginResult.message.timestamp) {
var timestamp = new Date(pluginResult.message.timestamp);
pluginResult.message.timestamp = timestamp;
}
return pluginResult;
};
PhoneGap.addConstructor(function() {
if (typeof navigator.compass === "undefined") {
navigator.compass = new Compass();
+124 -194
View File
@@ -11,8 +11,6 @@ PhoneGap.addResource("file");
/**
* This class provides some useful information about a file.
* This is the fields returned when navigator.fileMgr.getFileProperties()
* is called.
* @constructor
*/
var FileProperties = function(filePath) {
@@ -32,9 +30,9 @@ var FileProperties = function(filePath) {
* @param size {Number} size of the file in bytes
*/
var File = function(name, fullPath, type, lastModifiedDate, size) {
this.name = name || null;
this.name = name || null;
this.fullPath = fullPath || null;
this.type = type || null;
this.type = type || null;
this.lastModifiedDate = lastModifiedDate || null;
this.size = size || 0;
};
@@ -61,64 +59,9 @@ FileError.QUOTA_EXCEEDED_ERR = 10;
FileError.TYPE_MISMATCH_ERR = 11;
FileError.PATH_EXISTS_ERR = 12;
//-----------------------------------------------------------------------------
// File manager
//-----------------------------------------------------------------------------
/** @constructor */
var FileMgr = function() {
};
FileMgr.prototype.getFileProperties = function(filePath) {
return PhoneGap.exec(null, null, "File", "getFileProperties", [filePath]);
};
FileMgr.prototype.getFileBasePaths = function() {
};
FileMgr.prototype.testSaveLocationExists = function(successCallback, errorCallback) {
return PhoneGap.exec(successCallback, errorCallback, "File", "testSaveLocationExists", []);
};
FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) {
return PhoneGap.exec(successCallback, errorCallback, "File", "testFileExists", [fileName]);
};
FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) {
return PhoneGap.exec(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]);
};
FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) {
return PhoneGap.exec(successCallback, errorCallback, "File", "getFreeDiskSpace", []);
};
FileMgr.prototype.write = function(fileName, data, position, successCallback, errorCallback) {
PhoneGap.exec(successCallback, errorCallback, "File", "write", [fileName, data, position]);
};
FileMgr.prototype.truncate = function(fileName, size, successCallback, errorCallback) {
PhoneGap.exec(successCallback, errorCallback, "File", "truncate", [fileName, size]);
};
FileMgr.prototype.readAsText = function(fileName, encoding, successCallback, errorCallback) {
PhoneGap.exec(successCallback, errorCallback, "File", "readAsText", [fileName, encoding]);
};
FileMgr.prototype.readAsDataURL = function(fileName, successCallback, errorCallback) {
PhoneGap.exec(successCallback, errorCallback, "File", "readAsDataURL", [fileName]);
};
PhoneGap.addConstructor(function() {
if (typeof navigator.fileMgr === "undefined") {
navigator.fileMgr = new FileMgr();
}
});
//-----------------------------------------------------------------------------
// File Reader
//-----------------------------------------------------------------------------
// TODO: All other FileMgr function operate on the SD card as root. However,
// for FileReader & FileWriter the root is not SD card. Should this be changed?
/**
* This class reads the mobile device file system.
@@ -188,11 +131,11 @@ FileReader.prototype.abort = function() {
*/
FileReader.prototype.readAsText = function(file, encoding) {
this.fileName = "";
if (typeof file.fullPath === "undefined") {
this.fileName = file;
} else {
this.fileName = file.fullPath;
}
if (typeof file.fullPath === "undefined") {
this.fileName = file;
} else {
this.fileName = file.fullPath;
}
// LOADING state
this.readyState = FileReader.LOADING;
@@ -208,8 +151,7 @@ FileReader.prototype.readAsText = function(file, encoding) {
var me = this;
// Read file
navigator.fileMgr.readAsText(this.fileName, enc,
PhoneGap.exec(
// Success callback
function(r) {
var evt;
@@ -235,89 +177,6 @@ FileReader.prototype.readAsText = function(file, encoding) {
me.onloadend({"type":"loadend", "target":me});
}
},
// Error callback
function(e) {
var evt;
// If DONE (cancelled), then don't do anything
if (me.readyState === FileReader.DONE) {
return;
}
// Save error
me.error = e;
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror({"type":"error", "target":me});
}
// DONE state
me.readyState = FileReader.DONE;
// If onloadend callback
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
}
}
);
};
/**
* Read file and return data as a base64 encoded data url.
* A data url is of the form:
* data:[<mediatype>][;base64],<data>
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsDataURL = function(file) {
this.fileName = "";
if (typeof file.fullPath === "undefined") {
this.fileName = file;
} else {
this.fileName = file.fullPath;
}
// LOADING state
this.readyState = FileReader.LOADING;
// If loadstart callback
if (typeof this.onloadstart === "function") {
this.onloadstart({"type":"loadstart", "target":this});
}
var me = this;
// Read file
navigator.fileMgr.readAsDataURL(this.fileName,
// Success callback
function(r) {
var evt;
// If DONE (cancelled), then don't do anything
if (me.readyState === FileReader.DONE) {
return;
}
// Save result
me.result = r;
// If onload callback
if (typeof me.onload === "function") {
me.onload({"type":"load", "target":me});
}
// DONE state
me.readyState = FileReader.DONE;
// If onloadend callback
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
}
},
// Error callback
function(e) {
var evt;
@@ -341,8 +200,86 @@ FileReader.prototype.readAsDataURL = function(file) {
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
}
}
);
}, "File", "readAsText", [this.fileName, enc]);
};
/**
* Read file and return data as a base64 encoded data url.
* A data url is of the form:
* data:[<mediatype>][;base64],<data>
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsDataURL = function(file) {
this.fileName = "";
if (typeof file.fullPath === "undefined") {
this.fileName = file;
} else {
this.fileName = file.fullPath;
}
// LOADING state
this.readyState = FileReader.LOADING;
// If loadstart callback
if (typeof this.onloadstart === "function") {
this.onloadstart({"type":"loadstart", "target":this});
}
var me = this;
// Read file
PhoneGap.exec(
// Success callback
function(r) {
var evt;
// If DONE (cancelled), then don't do anything
if (me.readyState === FileReader.DONE) {
return;
}
// Save result
me.result = r;
// If onload callback
if (typeof me.onload === "function") {
me.onload({"type":"load", "target":me});
}
// DONE state
me.readyState = FileReader.DONE;
// If onloadend callback
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
}
},
// Error callback
function(e) {
var evt;
// If DONE (cancelled), then don't do anything
if (me.readyState === FileReader.DONE) {
return;
}
// Save error
me.error = e;
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror({"type":"error", "target":me});
}
// DONE state
me.readyState = FileReader.DONE;
// If onloadend callback
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
}
}, "File", "readAsDataURL", [this.fileName]);
};
/**
@@ -383,10 +320,10 @@ FileReader.prototype.readAsArrayBuffer = function(file) {
var FileWriter = function(file) {
this.fileName = "";
this.length = 0;
if (file) {
this.fileName = file.fullPath || file;
this.length = file.size || 0;
}
if (file) {
this.fileName = file.fullPath || file;
this.length = file.size || 0;
}
// default is to write at the beginning of the file
this.position = 0;
@@ -398,12 +335,12 @@ var FileWriter = function(file) {
this.error = null;
// Event handlers
this.onwritestart = null; // When writing starts
this.onprogress = null; // While writing the file, and reporting partial file data
this.onwrite = null; // When the write has successfully completed.
this.onwriteend = null; // When the request has completed (either in success or failure).
this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method.
this.onerror = null; // When the write has failed (see errors).
this.onwritestart = null; // When writing starts
this.onprogress = null; // While writing the file, and reporting partial file data
this.onwrite = null; // When the write has successfully completed.
this.onwriteend = null; // When the request has completed (either in success or failure).
this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method.
this.onerror = null; // When the write has failed (see errors).
};
// States
@@ -416,9 +353,9 @@ FileWriter.DONE = 2;
*/
FileWriter.prototype.abort = function() {
// check for invalid state
if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
throw FileError.INVALID_STATE_ERR;
}
if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
throw FileError.INVALID_STATE_ERR;
}
// set error
var error = new FileError(), evt;
@@ -448,10 +385,10 @@ FileWriter.prototype.abort = function() {
* @param text to be written
*/
FileWriter.prototype.write = function(text) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw FileError.INVALID_STATE_ERR;
}
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw FileError.INVALID_STATE_ERR;
}
// WRITING state
this.readyState = FileWriter.WRITING;
@@ -464,8 +401,7 @@ FileWriter.prototype.write = function(text) {
}
// Write file
navigator.fileMgr.write(this.fileName, text, this.position,
PhoneGap.exec(
// Success callback
function(r) {
var evt;
@@ -492,7 +428,6 @@ FileWriter.prototype.write = function(text) {
me.onwriteend({"type":"writeend", "target":me});
}
},
// Error callback
function(e) {
var evt;
@@ -517,9 +452,7 @@ FileWriter.prototype.write = function(text) {
if (typeof me.onwriteend === "function") {
me.onwriteend({"type":"writeend", "target":me});
}
}
);
}, "File", "write", [this.fileName, text, this.position]);
};
/**
@@ -543,18 +476,18 @@ FileWriter.prototype.seek = function(offset) {
// See back from end of file.
if (offset < 0) {
this.position = Math.max(offset + this.length, 0);
}
this.position = Math.max(offset + this.length, 0);
}
// Offset is bigger then file size so set position
// to the end of the file.
else if (offset > this.length) {
this.position = this.length;
}
else if (offset > this.length) {
this.position = this.length;
}
// Offset is between 0 and file size so set the position
// to start writing.
else {
this.position = offset;
}
else {
this.position = offset;
}
};
/**
@@ -563,10 +496,10 @@ FileWriter.prototype.seek = function(offset) {
* @param size to chop the file at.
*/
FileWriter.prototype.truncate = function(size) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw FileError.INVALID_STATE_ERR;
}
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw FileError.INVALID_STATE_ERR;
}
// WRITING state
this.readyState = FileWriter.WRITING;
@@ -579,8 +512,7 @@ FileWriter.prototype.truncate = function(size) {
}
// Write file
navigator.fileMgr.truncate(this.fileName, size,
PhoneGap.exec(
// Success callback
function(r) {
var evt;
@@ -606,7 +538,6 @@ FileWriter.prototype.truncate = function(size) {
me.onwriteend({"type":"writeend", "target":me});
}
},
// Error callback
function(e) {
var evt;
@@ -630,8 +561,7 @@ FileWriter.prototype.truncate = function(size) {
if (typeof me.onwriteend === "function") {
me.onwriteend({"type":"writeend", "target":me});
}
}
);
}, "File", "truncate", [this.fileName, size]);
};
/**
@@ -1050,10 +980,10 @@ LocalFileSystem.prototype._castDate = function(pluginResult) {
* Add the FileSystem interface into the browser.
*/
PhoneGap.addConstructor(function() {
var pgLocalFileSystem = new LocalFileSystem();
// Needed for cast methods
var pgLocalFileSystem = new LocalFileSystem();
// Needed for cast methods
if(typeof window.localFileSystem == "undefined") window.localFileSystem = pgLocalFileSystem;
if(typeof window.requestFileSystem == "undefined") window.requestFileSystem = pgLocalFileSystem.requestFileSystem;
if(typeof window.resolveLocalFileSystemURI == "undefined") window.resolveLocalFileSystemURI = pgLocalFileSystem.resolveLocalFileSystemURI;
});
}
}
+5 -1
View File
@@ -53,10 +53,14 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro
var fileName = null;
var mimeType = null;
var params = null;
var chunkedMode = true;
if (options) {
fileKey = options.fileKey;
fileName = options.fileName;
mimeType = options.mimeType;
if (options.chunkedMode != null || typeof options.chunkedMode != "undefined") {
chunkedMode = options.chunkedMode;
}
if (options.params) {
params = options.params;
}
@@ -65,7 +69,7 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro
}
}
PhoneGap.exec(successCallback, errorCallback, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, debug]);
PhoneGap.exec(successCallback, errorCallback, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode]);
};
/**
+1 -1
View File
@@ -1,7 +1,7 @@
<html>
<head>
<title></title>
<script src="phonegap-1.0.0.js"></script>
<script src="phonegap-1.1.0.js"></script>
</head>
<body>
+4 -4
View File
@@ -109,7 +109,7 @@
<target name="build-javascript">
<!-- Clean up existing files -->
<delete file="assets/www/phonegap_${version}.js"/>
<!--<delete file="assets/www/phonegap_${version}.js"/>-->
<!-- Create uncompressed JS file -->
<concat destfile="assets/www/phonegap-${version}.js">
@@ -119,15 +119,15 @@
<!-- update project files to reference phonegap-x.x.x.min.js -->
<replaceregexp match="phonegap(.*)\.js" replace="phonegap-${version}.js" byline="true">
<fileset file="assets/www/index.html" />
<fileset file="../example/index.html" />
<fileset file="assets/www/index.html" />
<!-- <fileset file="../bin/templates/project/assets/www/index.html" /> -->
</replaceregexp>
<replaceregexp match="phonegapVersion = [\u0022].*[\u0022];" replace="phonegapVersion = ${dblQuote}${version}${dblQuote};" byline="true">
<fileset file="src/com/phonegap/Device.java" />
</replaceregexp>
<!-- Delete temp file -->
<delete file="assets/www/phonegap-tmp.js"/>
<!--<delete file="assets/www/phonegap-tmp.js"/>-->
</target>
<!-- Build PhoneGap jar file that includes all native code, and PhoneGap JS file
+4
View File
@@ -1,4 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<phonegap>
<access origin="http://127.0.0.1*"/>
<<<<<<< HEAD
=======
<log level="DEBUG"/>
>>>>>>> 7ee04ebf313c7403cf623a1f9c00b6b5e7cc9f0b
</phonegap>
+28 -1
View File
@@ -10,6 +10,7 @@ package com.phonegap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
import java.util.HashMap;
@@ -43,6 +44,9 @@ public class App extends Plugin {
}
else if (action.equals("clearHistory")) {
this.clearHistory();
}
else if (action.equals("backHistory")) {
this.backHistory();
}
else if (action.equals("overrideBackbutton")) {
this.overrideBackbutton(args.getBoolean(0));
@@ -54,6 +58,9 @@ public class App extends Plugin {
else if (action.equals("exitApp")) {
this.exitApp();
}
else if (action.equals("addWhiteListEntry")) {
this.addWhiteListEntry(args.getString(0), args.optBoolean(1));
}
return new PluginResult(status, result);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
@@ -140,9 +147,19 @@ public class App extends Plugin {
/**
* Clear web history in this web view.
* This does not have any effect since each page has its own activity.
*/
public void clearHistory() {
((DroidGap)this.ctx).clearHistory();
// TODO: Kill previous activities?
}
/**
* Go to previous page displayed.
* This is the same as pressing the backbutton on Android device.
*/
public void backHistory() {
((DroidGap)this.ctx).endActivity();
}
/**
@@ -169,7 +186,17 @@ public class App extends Plugin {
* Exit the Android application.
*/
public void exitApp() {
((DroidGap)this.ctx).finish();
((DroidGap)this.ctx).setResult(Activity.RESULT_OK);
((DroidGap)this.ctx).endActivity();
}
/**
* Add entry to approved list of URLs (whitelist)
*
* @param origin URL regular expression to allow
* @param subdomains T=include all subdomains under origin
*/
public void addWhiteListEntry(String origin, boolean subdomains) {
((DroidGap)this.ctx).addWhiteListEntry(origin, subdomains);
}
}
+1 -1
View File
@@ -313,7 +313,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* @return T=streaming, F=local
*/
public boolean isStreaming(String file) {
if (file.contains("http://")) {
if (file.contains("http://") || file.contains("https://")) {
return true;
}
else {
+148 -46
View File
@@ -10,12 +10,14 @@ package com.phonegap;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.codec.binary.Base64;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
@@ -23,9 +25,11 @@ import com.phonegap.api.PluginResult;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
/**
@@ -42,16 +46,27 @@ public class CameraLauncher extends Plugin {
private static final int CAMERA = 1; // Take picture from camera
private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture library (same as PHOTOLIBRARY for Android)
private static final int PICTURE = 0; // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
private static final int VIDEO = 1; // allow selection of video only, ONLY RETURNS URL
private static final int ALLMEDIA = 2; // allow selection from all media types
private static final int JPEG = 0; // Take a picture of type JPEG
private static final int PNG = 1; // Take a picture of type PNG
private static final String GET_PICTURE = "Get Picture";
private static final String GET_VIDEO = "Get Video";
private static final String GET_All = "Get All";
private static final String LOG_TAG = "CameraLauncher";
private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
private int targetWidth; // desired width of the image
private int targetHeight; // desired height of the image
private Uri imageUri;
private int encodingType;
// Uri of captured image
private Uri imageUri; // Uri of captured image
private int encodingType; // Type of encoding to use
private int mediaType; // What type of media to retrieve
public String callbackId;
private int numPics;
/**
* Constructor.
@@ -74,29 +89,30 @@ public class CameraLauncher extends Plugin {
try {
if (action.equals("takePicture")) {
int destType = DATA_URL;
if (args.length() > 1) {
destType = args.getInt(1);
}
int srcType = CAMERA;
if (args.length() > 2) {
srcType = args.getInt(2);
}
if (args.length() > 3) {
this.targetWidth = args.getInt(3);
}
if (args.length() > 4) {
this.targetHeight = args.getInt(4);
}
int destType = DATA_URL;
this.targetHeight = 0;
this.targetWidth = 0;
this.encodingType = JPEG;
if (args.length() > 5) {
this.encodingType = args.getInt(5);
this.mediaType = PICTURE;
this.mQuality = 80;
JSONObject options = args.optJSONObject(0);
if (options != null) {
srcType = options.getInt("sourceType");
destType = options.getInt("destinationType");
this.targetHeight = options.getInt("targetHeight");
this.targetWidth = options.getInt("targetWidth");
this.encodingType = options.getInt("encodingType");
this.mediaType = options.getInt("mediaType");
this.mQuality = options.getInt("quality");
}
if (srcType == CAMERA) {
this.takePicture(args.getInt(0), destType, encodingType);
this.takePicture(destType, encodingType);
}
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
this.getImage(args.getInt(0), srcType, destType);
this.getImage(srcType, destType);
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
@@ -127,9 +143,10 @@ public class CameraLauncher extends Plugin {
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
* @param returnType Set the type of image to return.
*/
public void takePicture(int quality, int returnType, int encodingType) {
this.mQuality = quality;
public void takePicture(int returnType, int encodingType) {
// Save the number of images currently on disk for later
this.numPics = queryImgDB().getCount();
// Display camera
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
@@ -166,15 +183,27 @@ public class CameraLauncher extends Plugin {
* @param returnType Set the type of image to return.
*/
// TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do!
public void getImage(int quality, int srcType, int returnType) {
this.mQuality = quality;
public void getImage(int srcType, int returnType) {
Intent intent = new Intent();
intent.setType("image/*");
String title = GET_PICTURE;
if (this.mediaType == PICTURE) {
intent.setType("image/*");
}
else if (this.mediaType == VIDEO) {
intent.setType("video/*");
title = GET_VIDEO;
}
else if (this.mediaType == ALLMEDIA) {
// I wanted to make the type 'image/*, video/*' but this does not work on all versions
// of android so I had to go with the wildcard search.
intent.setType("*/*");
title = GET_All;
}
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
this.ctx.startActivityForResult((Plugin) this, Intent.createChooser(intent,
new String("Get Picture")), (srcType+1)*16 + returnType + 1);
new String(title)), (srcType+1)*16 + returnType + 1);
}
/**
@@ -234,7 +263,7 @@ public class CameraLauncher extends Plugin {
// Get src and dest types from request code
int srcType = (requestCode/16) - 1;
int destType = (requestCode % 16) - 1;
// If CAMERA
if (srcType == CAMERA) {
// If image available
@@ -262,6 +291,7 @@ public class CameraLauncher extends Plugin {
// If sending base64 image back
if (destType == DATA_URL) {
this.processPicture(bitmap);
checkForDuplicateImage(DATA_URL);
}
// If sending filename back
@@ -301,6 +331,8 @@ public class CameraLauncher extends Plugin {
bitmap.recycle();
bitmap = null;
System.gc();
checkForDuplicateImage(FILE_URI);
} catch (IOException e) {
e.printStackTrace();
this.failPicture("Error capturing image.");
@@ -323,24 +355,55 @@ public class CameraLauncher extends Plugin {
if (resultCode == Activity.RESULT_OK) {
Uri uri = intent.getData();
android.content.ContentResolver resolver = this.ctx.getContentResolver();
// If sending base64 image back
if (destType == DATA_URL) {
try {
Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
bitmap = scaleBitmap(bitmap);
this.processPicture(bitmap);
bitmap.recycle();
bitmap = null;
System.gc();
} catch (FileNotFoundException e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
// If sending filename back
else if (destType == FILE_URI) {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
// If you ask for video or all media type you will automatically get back a file URI
// and there will be no attempt to resize any returned data
if (this.mediaType != PICTURE) {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
else {
// If sending base64 image back
if (destType == DATA_URL) {
try {
Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
bitmap = scaleBitmap(bitmap);
this.processPicture(bitmap);
bitmap.recycle();
bitmap = null;
System.gc();
} catch (FileNotFoundException e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
// If sending filename back
else if (destType == FILE_URI) {
// Do we need to scale the returned file
if (this.targetHeight > 0 && this.targetWidth > 0) {
try {
Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
bitmap = scaleBitmap(bitmap);
String fileName = DirectoryManager.getTempDirectoryPath(ctx) + "/resize.jpg";
OutputStream os = new FileOutputStream(fileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
os.close();
bitmap.recycle();
bitmap = null;
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + fileName)), this.callbackId);
System.gc();
} catch (Exception e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
else {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
}
}
}
else if (resultCode == Activity.RESULT_CANCELED) {
@@ -351,7 +414,46 @@ public class CameraLauncher extends Plugin {
}
}
}
/**
* Creates a cursor that can be used to determine how many images we have.
*
* @return a cursor
*/
private Cursor queryImgDB() {
return this.ctx.getContentResolver().query(
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID },
null,
null,
null);
}
/**
* Used to find out if we are in a situation where the Camera Intent adds to images
* to the content store. If we are using a FILE_URI and the number of images in the DB
* increases by 2 we have a duplicate, when using a DATA_URL the number is 1.
*
* @param type FILE_URI or DATA_URL
*/
private void checkForDuplicateImage(int type) {
int diff = 1;
Cursor cursor = queryImgDB();
int currentNumOfImages = cursor.getCount();
if (type == FILE_URI) {
diff = 2;
}
// delete the duplicate file if the difference is 2 for file URI or 1 for Data URL
if ((currentNumOfImages - numPics) == diff) {
cursor.moveToLast();
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))) - 1;
Uri uri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "/" + id);
this.ctx.getContentResolver().delete(uri, null, null);
}
}
/**
* Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript.
*
+23 -5
View File
@@ -29,6 +29,10 @@ import com.phonegap.api.PluginResult;
public class Capture extends Plugin {
private static final String VIDEO_3GPP = "video/3gpp";
private static final String AUDIO_3GPP = "audio/3gpp";
private static final String IMAGE_JPEG = "image/jpeg";
private static final int CAPTURE_AUDIO = 0; // Constant for capture audio
private static final int CAPTURE_IMAGE = 1; // Constant for capture image
private static final int CAPTURE_VIDEO = 2; // Constant for capture video
@@ -97,14 +101,15 @@ public class Capture extends Plugin {
if (mimeType == null || mimeType.equals("")) {
mimeType = FileUtils.getMimeType(filePath);
}
Log.d(LOG_TAG, "Mime type = " + mimeType);
if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) {
if (mimeType.equals(IMAGE_JPEG) || filePath.endsWith(".jpg")) {
obj = getImageData(filePath, obj);
}
else if (filePath.endsWith("audio/3gpp")) {
else if (mimeType.endsWith(AUDIO_3GPP)) {
obj = getAudioVideoData(filePath, obj, false);
}
else if (mimeType.equals("video/3gpp")) {
else if (mimeType.equals(VIDEO_3GPP)) {
obj = getAudioVideoData(filePath, obj, true);
}
}
@@ -233,7 +238,7 @@ public class Capture extends Plugin {
// Create entry in media store for image
// (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
ContentValues values = new ContentValues();
values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, IMAGE_JPEG);
Uri uri = null;
try {
uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
@@ -329,7 +334,20 @@ public class Capture extends Plugin {
// File properties
obj.put("name", fp.getName());
obj.put("fullPath", fp.getAbsolutePath());
obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath()));
// Because of an issue with MimeTypeMap.getMimeTypeFromExtension() all .3gpp files
// are reported as video/3gpp. I'm doing this hacky check of the URI to see if it
// is stored in the audio or video content store.
if (fp.getAbsoluteFile().toString().endsWith(".3gp") || fp.getAbsoluteFile().toString().endsWith(".3gpp")) {
if (data.toString().contains("/audio/")) {
obj.put("type", AUDIO_3GPP);
} else {
obj.put("type", VIDEO_3GPP);
}
} else {
obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath()));
}
obj.put("lastModifiedDate", fp.lastModified());
obj.put("size", fp.length());
} catch (JSONException e) {
+205 -181
View File
@@ -11,6 +11,7 @@ import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.phonegap.api.PhonegapActivity;
import com.phonegap.api.Plugin;
@@ -27,129 +28,130 @@ import android.content.Context;
*/
public class CompassListener 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 long TIMEOUT = 30000; // Timeout in msec to shut off listener
int status; // status of listener
float heading; // most recent heading value
long timeStamp; // time of most recent value
long lastAccessTime; // time the value was last retrieved
public long TIMEOUT = 30000; // Timeout in msec to shut off listener
int status; // status of listener
float heading; // most recent heading value
long timeStamp; // time of most recent value
long lastAccessTime; // time the value was last retrieved
int accuracy; // accuracy of the sensor
private SensorManager sensorManager;// Sensor manager
Sensor mSensor; // Compass sensor returned by sensor manager
/**
* Constructor.
*/
public CompassListener() {
Sensor mSensor; // Compass sensor returned by sensor manager
/**
* Constructor.
*/
public CompassListener() {
this.timeStamp = 0;
this.setStatus(CompassListener.STOPPED);
}
}
/**
* Sets the context of the Command. This can then be used to do things like
* get file paths associated with the Activity.
*
* @param ctx The context of the main Activity.
*/
public void setContext(PhonegapActivity 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(PhonegapActivity 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("start")) {
this.start();
}
else if (action.equals("stop")) {
this.stop();
}
else if (action.equals("getStatus")) {
int i = this.getStatus();
return new PluginResult(status, i);
}
else if (action.equals("getHeading")) {
// If not running, then this is an async call, so don't worry about waiting
if (this.status != RUNNING) {
int r = this.start();
if (r == ERROR_FAILED_TO_START) {
return new PluginResult(PluginResult.Status.IO_EXCEPTION, 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);
}
}
float f = this.getHeading();
return new PluginResult(status, f);
}
else if (action.equals("setTimeout")) {
this.setTimeout(args.getLong(0));
}
else if (action.equals("getTimeout")) {
long l = this.getTimeout();
return new PluginResult(status, l);
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
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.OK;
String result = "";
try {
if (action.equals("start")) {
this.start();
}
else if (action.equals("stop")) {
this.stop();
}
else if (action.equals("getStatus")) {
int i = this.getStatus();
return new PluginResult(status, i);
}
else if (action.equals("getHeading")) {
// If not running, then this is an async call, so don't worry about waiting
if (this.status != RUNNING) {
int r = this.start();
if (r == ERROR_FAILED_TO_START) {
return new PluginResult(PluginResult.Status.IO_EXCEPTION, 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);
}
}
//float f = this.getHeading();
return new PluginResult(status, getCompassHeading(), "navigator.compass._castDate");
}
else if (action.equals("setTimeout")) {
this.setTimeout(args.getLong(0));
}
else if (action.equals("getTimeout")) {
long l = this.getTimeout();
return new PluginResult(status, l);
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
if (action.equals("getStatus")) {
return true;
}
else if (action.equals("getHeading")) {
// Can only return value if RUNNING
if (this.status == 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("getStatus")) {
return true;
}
else if (action.equals("getHeading")) {
// Can only return value if RUNNING
if (this.status == RUNNING) {
return true;
}
}
else if (action.equals("getTimeout")) {
return true;
}
return false;
}
/**
* Called when listener is to be shut down and object is being destroyed.
*/
public void onDestroy() {
this.stop();
}
public void onDestroy() {
this.stop();
}
//--------------------------------------------------------------------------
// LOCAL METHODS
@@ -158,113 +160,135 @@ public class CompassListener extends Plugin implements SensorEventListener {
/**
* Start listening for compass sensor.
*
* @return status of listener
* @return status of listener
*/
public int start() {
// If already starting or running, then just return
public int start() {
// If already starting or running, then just return
if ((this.status == CompassListener.RUNNING) || (this.status == CompassListener.STARTING)) {
return this.status;
return this.status;
}
// Get accelerometer from sensor manager
List<Sensor> list = this.sensorManager.getSensorList(Sensor.TYPE_ORIENTATION);
// Get accelerometer from sensor manager
List<Sensor> list = this.sensorManager.getSensorList(Sensor.TYPE_ORIENTATION);
// If found, then register as listener
if (list.size() > 0) {
this.mSensor = list.get(0);
this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_NORMAL);
if (list.size() > 0) {
this.mSensor = list.get(0);
this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_NORMAL);
this.lastAccessTime = System.currentTimeMillis();
this.setStatus(CompassListener.STARTING);
}
}
// If error, then set status to error
// If error, then set status to error
else {
this.setStatus(CompassListener.ERROR_FAILED_TO_START);
}
return this.status;
}
}
/**
* Stop listening to compass sensor.
*/
public void stop() {
public void stop() {
if (this.status != CompassListener.STOPPED) {
this.sensorManager.unregisterListener(this);
this.sensorManager.unregisterListener(this);
}
this.setStatus(CompassListener.STOPPED);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
}
/**
* Sensor listener event.
*
* @param SensorEvent event
*/
public void onSensorChanged(SensorEvent event) {
public void onSensorChanged(SensorEvent event) {
// We only care about the orientation as far as it refers to Magnetic North
float heading = event.values[0];
// We only care about the orientation as far as it refers to Magnetic North
float heading = event.values[0];
// Save heading
// Save heading
this.timeStamp = System.currentTimeMillis();
this.heading = heading;
this.setStatus(CompassListener.RUNNING);
this.heading = heading;
this.setStatus(CompassListener.RUNNING);
// If heading hasn't been read for TIMEOUT time, then turn off compass sensor to save power
if ((this.timeStamp - this.lastAccessTime) > this.TIMEOUT) {
this.stop();
}
}
// If heading hasn't been read for TIMEOUT time, then turn off compass sensor to save power
if ((this.timeStamp - this.lastAccessTime) > this.TIMEOUT) {
this.stop();
}
}
/**
* Get status of compass sensor.
*
* @return status
* @return status
*/
public int getStatus() {
return this.status;
}
/**
* Get the most recent compass heading.
*
* @return heading
*/
public float getHeading() {
public int getStatus() {
return this.status;
}
/**
* Get the most recent compass heading.
*
* @return heading
*/
public float getHeading() {
this.lastAccessTime = System.currentTimeMillis();
return this.heading;
}
/**
* Set the timeout to turn off compass sensor if getHeading() hasn't been called.
*
* @param timeout Timeout in msec.
*/
public void setTimeout(long timeout) {
this.TIMEOUT = timeout;
}
/**
* Get the timeout to turn off compass sensor if getHeading() hasn't been called.
*
* @return timeout in msec
*/
public long getTimeout() {
return this.TIMEOUT;
}
return this.heading;
}
/**
* Set the timeout to turn off compass sensor if getHeading() hasn't been called.
*
* @param timeout Timeout in msec.
*/
public void setTimeout(long timeout) {
this.TIMEOUT = timeout;
}
/**
* Get the timeout to turn off compass sensor if getHeading() hasn't been called.
*
* @return timeout in msec
*/
public long getTimeout() {
return this.TIMEOUT;
}
/**
* Set the status and send it to JavaScript.
* @param status
*/
private void setStatus(int status) {
this.status = status;
}
/**
* Set the status and send it to JavaScript.
* @param status
*/
private void setStatus(int status) {
this.status = status;
}
/**
* Create the CompassHeading JSON object to be returned to JavaScript
*
* @return a compass heading
*/
private JSONObject getCompassHeading() {
JSONObject obj = new JSONObject();
try {
obj.put("magneticHeading", this.getHeading());
obj.put("trueHeading", this.getHeading());
// Since the magnetic and true heading are always the same our and accuracy
// is defined as the difference between true and magnetic always return zero
obj.put("headingAccuracy", 0);
obj.put("timestamp", this.timeStamp);
} catch (JSONException e) {
// Should never happen
}
return obj;
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ import android.provider.Settings;
public class Device extends Plugin {
public static String phonegapVersion = "1.0.0"; // PhoneGap version
public static String phonegapVersion = "1.1.0"; // PhoneGap version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID
File diff suppressed because it is too large Load Diff
+14 -5
View File
@@ -81,9 +81,10 @@ public class FileTransfer extends Plugin {
try {
JSONObject params = args.optJSONObject(5);
boolean trustEveryone = args.optBoolean(6);
boolean chunkedMode = args.getBoolean(7);
if (action.equals("upload")) {
FileUploadResult r = upload(file, server, fileKey, fileName, mimeType, params, trustEveryone);
FileUploadResult r = upload(file, server, 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 {
@@ -202,7 +203,7 @@ public class FileTransfer extends Plugin {
* @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) throws IOException, SSLException {
final String mimeType, JSONObject params, boolean trustEveryone, boolean chunkedMode) throws IOException, SSLException {
// Create return object
FileUploadResult result = new FileUploadResult();
@@ -240,7 +241,7 @@ public class FileTransfer extends Plugin {
conn = https;
}
}
// Return a standard HTTP conneciton
// Return a standard HTTP connection
else {
conn = (HttpURLConnection) url.openConnection();
}
@@ -264,7 +265,12 @@ public class FileTransfer extends Plugin {
if (cookie != null) {
conn.setRequestProperty("Cookie", cookie);
}
// Should set this up as an option
if (chunkedMode) {
conn.setChunkedStreamingMode(maxBufferSize);
}
dos = new DataOutputStream( conn.getOutputStream() );
// Send any extra parameters
@@ -274,7 +280,7 @@ public class FileTransfer extends Plugin {
dos.writeBytes(LINE_START + BOUNDRY + LINE_END);
dos.writeBytes("Content-Disposition: form-data; name=\"" + key.toString() + "\";");
dos.writeBytes(LINE_END + LINE_END);
dos.writeBytes(params.getString(key.toString()));
dos.write(params.getString(key.toString()).getBytes());
dos.writeBytes(LINE_END);
}
} catch (JSONException e) {
@@ -357,6 +363,9 @@ public class FileTransfer extends Plugin {
Uri uri = Uri.parse(path);
return ctx.getContentResolver().openInputStream(uri);
}
else if (path.startsWith("file://")) {
return new FileInputStream(path.substring(7));
}
else {
return new FileInputStream(path);
}
+13 -1
View File
@@ -120,7 +120,7 @@ public class FileUtils extends Plugin {
else if (action.equals("requestFileSystem")) {
long size = args.optLong(1);
if (size != 0) {
if (size > DirectoryManager.getFreeDiskSpace()) {
if (size > (DirectoryManager.getFreeDiskSpace()*1024)) {
JSONObject error = new JSONObject().put("code", FileUtils.QUOTA_EXCEEDED_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
}
@@ -158,6 +158,7 @@ public class FileUtils extends Plugin {
success = remove(args.getString(0));
if (success) {
notifyDelete(args.getString(0));
return new PluginResult(status);
} else {
JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR);
@@ -221,6 +222,17 @@ public class FileUtils extends Plugin {
}
/**
* Need to check to see if we need to clean up the content store
*
* @param filePath the path to check
*/
private void notifyDelete(String filePath) {
int result = this.ctx.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
MediaStore.Images.Media.DATA + " = ?",
new String[] {filePath});
}
/**
* Allows the user to look up the Entry for a file or directory referred to by a local URI.
*
* @param url of the file/directory to look up
@@ -41,6 +41,8 @@ public class NetworkManager extends Plugin {
public static final String HSPA = "hspa";
public static final String HSUPA = "hsupa";
public static final String HSDPA = "hsdpa";
public static final String ONEXRTT = "1xrtt";
public static final String EHRPD = "ehrpd";
// 4G network types
public static final String LTE = "lte";
public static final String UMB = "umb";
@@ -208,8 +210,10 @@ public class NetworkManager extends Plugin {
type.toLowerCase().equals(EDGE)) {
return TYPE_2G;
}
else if (type.toLowerCase().equals(CDMA) ||
else if (type.toLowerCase().startsWith(CDMA) ||
type.toLowerCase().equals(UMTS) ||
type.toLowerCase().equals(ONEXRTT) ||
type.toLowerCase().equals(EHRPD) ||
type.toLowerCase().equals(HSUPA) ||
type.toLowerCase().equals(HSDPA) ||
type.toLowerCase().equals(HSPA)) {
+8 -1
View File
@@ -87,4 +87,11 @@ public interface IPlugin {
*/
void onActivityResult(int requestCode, int resultCode, Intent intent);
}
/**
* By specifying a <url-filter> in plugins.xml you can map a URL (using startsWith atm) to this method.
*
* @param url The URL that is trying to be loaded in the PhoneGap webview.
* @return Return true to prevent the URL from loading. Default is false.
*/
boolean onOverrideUrlLoading(String url);
}
+223
View File
@@ -0,0 +1,223 @@
/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
*/
package com.phonegap.api;
import android.util.Log;
/**
* Log to Android logging system.
*
* Log message can be a string or a printf formatted string with arguments.
* See http://developer.android.com/reference/java/util/Formatter.html
*/
public class LOG {
public static final int VERBOSE = Log.VERBOSE;
public static final int DEBUG = Log.DEBUG;
public static final int INFO = Log.INFO;
public static final int WARN = Log.WARN;
public static final int ERROR = Log.ERROR;
// Current log level
public static int LOGLEVEL = Log.ERROR;
/**
* Set the current log level.
*
* @param logLevel
*/
public static void setLogLevel(int logLevel) {
LOGLEVEL = logLevel;
Log.i("PhoneGapLog", "Changing log level to " + logLevel);
}
/**
* Set the current log level.
*
* @param logLevel
*/
public static void setLogLevel(String logLevel) {
if ("VERBOSE".equals(logLevel)) LOGLEVEL = VERBOSE;
else if ("DEBUG".equals(logLevel)) LOGLEVEL = DEBUG;
else if ("INFO".equals(logLevel)) LOGLEVEL = INFO;
else if ("WARN".equals(logLevel)) LOGLEVEL = WARN;
else if ("ERROR".equals(logLevel)) LOGLEVEL = ERROR;
Log.i("PhoneGapLog", "Changing log level to " + logLevel + "(" + LOGLEVEL + ")");
}
/**
* Determine if log level will be logged
*
* @param logLevel
* @return
*/
public static boolean isLoggable(int logLevel) {
return (logLevel >= LOGLEVEL);
}
/**
* Verbose log message.
*
* @param tag
* @param s
*/
public static void v(String tag, String s) {
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s);
}
/**
* Debug log message.
*
* @param tag
* @param s
*/
public static void d(String tag, String s) {
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s);
}
/**
* Info log message.
*
* @param tag
* @param s
*/
public static void i(String tag, String s) {
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s);
}
/**
* Warning log message.
*
* @param tag
* @param s
*/
public static void w(String tag, String s) {
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s);
}
/**
* Error log message.
*
* @param tag
* @param s
*/
public static void e(String tag, String s) {
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s);
}
/**
* Verbose log message.
*
* @param tag
* @param s
* @param e
*/
public static void v(String tag, String s, Throwable e) {
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s, e);
}
/**
* Debug log message.
*
* @param tag
* @param s
* @param e
*/
public static void d(String tag, String s, Throwable e) {
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s, e);
}
/**
* Info log message.
*
* @param tag
* @param s
* @param e
*/
public static void i(String tag, String s, Throwable e) {
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s, e);
}
/**
* Warning log message.
*
* @param tag
* @param s
* @param e
*/
public static void w(String tag, String s, Throwable e) {
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s, e);
}
/**
* Error log message.
*
* @param tag
* @param s
* @param e
*/
public static void e(String tag, String s, Throwable e) {
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s, e);
}
/**
* Verbose log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void v(String tag, String s, Object... args) {
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, String.format(s, args));
}
/**
* Debug log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void d(String tag, String s, Object... args) {
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, String.format(s, args));
}
/**
* Info log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void i(String tag, String s, Object... args) {
if (LOG.INFO >= LOGLEVEL) Log.i(tag, String.format(s, args));
}
/**
* Warning log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void w(String tag, String s, Object... args) {
if (LOG.WARN >= LOGLEVEL) Log.w(tag, String.format(s, args));
}
/**
* Error log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void e(String tag, String s, Object... args) {
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, String.format(s, args));
}
}
@@ -47,4 +47,11 @@ public abstract class PhonegapActivity extends Activity {
* @param plugin The plugin on which onActivityResult is to be called
*/
abstract public void setActivityResultCallback(IPlugin plugin);
/**
* Load the specified URL in the PhoneGap webview.
*
* @param url The URL to load.
*/
abstract public void loadUrl(String url);
}
@@ -104,6 +104,16 @@ public abstract class Plugin implements IPlugin {
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
}
/**
* By specifying a <url-filter> in plugins.xml you can map a URL (using startsWith atm) to this method.
*
* @param url The URL that is trying to be loaded in the PhoneGap webview.
* @return Return true to prevent the URL from loading. Default is false.
*/
public boolean onOverrideUrlLoading(String url) {
return false;
}
/**
* Send generic JavaScript statement back to JavaScript.
* success(...) and error(...) should be used instead where possible.
@@ -9,6 +9,7 @@ package com.phonegap.api;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import org.json.JSONArray;
@@ -17,6 +18,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.util.Log;
import android.webkit.WebView;
/**
@@ -25,7 +27,7 @@ import android.webkit.WebView;
* Calling native plugin code can be done by calling PluginManager.exec(...)
* from JavaScript.
*/
public final class PluginManager {
public final class PluginManager {
private HashMap<String, IPlugin> plugins = new HashMap<String,IPlugin>();
private HashMap<String, String> services = new HashMap<String,String>();
@@ -33,6 +35,10 @@ public final class PluginManager {
private final PhonegapActivity ctx;
private final WebView app;
// Map URL schemes like foo: to plugins that want to handle those schemes
// This would allow how all URLs are handled to be offloaded to a plugin
protected HashMap<String, String> urlMap = new HashMap<String,String>();
/**
* Constructor.
*
@@ -53,14 +59,17 @@ public final class PluginManager {
if (id == 0) { pluginConfigurationMissing(); }
XmlResourceParser xml = ctx.getResources().getXml(id);
int eventType = -1;
String pluginClass = "", pluginName = "";
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
String strNode = xml.getName();
if (strNode.equals("plugin")) {
String name = xml.getAttributeValue(null, "name");
String value = xml.getAttributeValue(null, "value");
pluginClass = xml.getAttributeValue(null, "value");
pluginName = xml.getAttributeValue(null, "name");
//System.out.println("Plugin: "+name+" => "+value);
this.addService(name, value);
this.addService(pluginName, pluginClass);
} else if (strNode.equals("url-filter")) {
this.urlMap.put(xml.getAttributeValue(null, "value"), pluginName);
}
}
try {
@@ -101,14 +110,9 @@ public final class PluginManager {
boolean runAsync = async;
try {
final JSONArray args = new JSONArray(jsonArgs);
String clazz = this.services.get(service);
Class c = null;
if (clazz != null) {
c = getClassByName(clazz);
}
if (isPhoneGapPlugin(c)) {
final IPlugin plugin = this.addPlugin(clazz, c);
final PhonegapActivity ctx = this.ctx;
final IPlugin plugin = this.getPlugin(service);
final PhonegapActivity ctx = this.ctx;
if (plugin != null) {
runAsync = async && !plugin.isSynch(action);
if (runAsync) {
// Run this on a different thread so that this one can return back to JS
@@ -150,8 +154,6 @@ public final class PluginManager {
}
}
}
} catch (ClassNotFoundException e) {
cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
} catch (JSONException e) {
System.out.println("ERROR: "+e.toString());
cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
@@ -175,7 +177,11 @@ public final class PluginManager {
*/
@SuppressWarnings("unchecked")
private Class getClassByName(final String clazz) throws ClassNotFoundException {
return Class.forName(clazz);
Class c = null;
if (clazz != null) {
c = Class.forName(clazz);
}
return c;
}
/**
@@ -203,18 +209,18 @@ public final class PluginManager {
* @return The plugin
*/
@SuppressWarnings("unchecked")
private IPlugin addPlugin(String className, Class clazz) {
if (this.plugins.containsKey(className)) {
return this.getPlugin(className);
}
try {
IPlugin plugin = (IPlugin)clazz.newInstance();
this.plugins.put(className, plugin);
plugin.setContext(this.ctx);
plugin.setView(this.app);
return plugin;
}
catch (Exception e) {
private IPlugin addPlugin(String pluginName, String className) {
try {
Class c = getClassByName(className);
if (isPhoneGapPlugin(c)) {
IPlugin plugin = (IPlugin)c.newInstance();
this.plugins.put(className, plugin);
plugin.setContext(this.ctx);
plugin.setView(this.app);
plugin.onResume(true);
return plugin;
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error adding plugin "+className+".");
}
@@ -224,12 +230,18 @@ public final class PluginManager {
/**
* Get the loaded plugin.
*
* If the plugin is not already loaded then load it.
*
* @param className The class of the loaded plugin.
* @return
*/
private IPlugin getPlugin(String className) {
IPlugin plugin = this.plugins.get(className);
return plugin;
private IPlugin getPlugin(String pluginName) {
String className = this.services.get(pluginName);
if (this.plugins.containsKey(className)) {
return this.plugins.get(className);
} else {
return this.addPlugin(pluginName, className);
}
}
/**
@@ -299,6 +311,23 @@ public final class PluginManager {
}
}
/**
* Called when the URL of the webview changes.
*
* @param url The URL that is being changed to.
* @return Return false to allow the URL to load, return true to prevent the URL from loading.
*/
public boolean onOverrideUrlLoading(String url) {
Iterator<Entry<String, String>> it = this.urlMap.entrySet().iterator();
while (it.hasNext()) {
HashMap.Entry<String, String> pairs = it.next();
if (url.startsWith(pairs.getKey())) {
return this.getPlugin(pairs.getValue()).onOverrideUrlLoading(url);
}
}
return false;
}
private void pluginConfigurationMissing() {
System.err.println("=====================================================================================");
System.err.println("ERROR: plugin.xml is missing. Add res/xml/plugins.xml to your project.");