From 0297807bd01f79444649fea482ca32241c92d5bf Mon Sep 17 00:00:00 2001 From: macdonst Date: Tue, 23 Aug 2011 01:38:00 +0800 Subject: [PATCH] Fix for issue #141: EXIF data stripped from captured photos in android In order to fix this issue I needed to read the EXIF data. Save it to a temporary object then after the bitmap is compressed I open the file and write the saved EXIF data. Supports the following EXIF fields if they are set in your image: APERTURE DATETIME EXPOSURE_TIME FLASH FOCAL_LENGTH GPS_ALTITUDE GPS_ALTITUDE_REF GPS_DATESTAMP GPS_LATITUDE GPS_LATITUDE_REF GPS_LONGITUDE GPS_LONGITUDE_REF GPS_PROCESSING_METHOD GPS_TIMESTAMP ISO MAKE MODEL ORIENTATION WHITE_BALANCE --- .../src/com/phonegap/CameraLauncher.java | 531 ++--- framework/src/com/phonegap/Capture.java | 564 +++-- framework/src/com/phonegap/DroidGap.java | 1824 ++++++++--------- framework/src/com/phonegap/ExifHelper.java | 153 ++ framework/src/com/phonegap/FileUtils.java | 20 +- 5 files changed, 1634 insertions(+), 1458 deletions(-) create mode 100644 framework/src/com/phonegap/ExifHelper.java diff --git a/framework/src/com/phonegap/CameraLauncher.java b/framework/src/com/phonegap/CameraLauncher.java index 6ca85c43..58526653 100755 --- a/framework/src/com/phonegap/CameraLauncher.java +++ b/framework/src/com/phonegap/CameraLauncher.java @@ -35,100 +35,102 @@ import android.util.Log; */ public class CameraLauncher extends Plugin { - private static final int DATA_URL = 0; // Return base64 encoded string - private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android) - - private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) - private static final int CAMERA = 1; // Take picture from camera - private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture library (same as PHOTOLIBRARY for Android) - - private static final int JPEG = 0; // Take a picture of type JPEG - private static final int PNG = 1; // Take a picture of type PNG - - private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - private int targetWidth; // desired width of the image - private int targetHeight; // desired height of the image - private Uri imageUri; // Uri of captured image - public String callbackId; - + private static final int DATA_URL = 0; // Return base64 encoded string + private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android) + + private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + private static final int CAMERA = 1; // Take picture from camera + private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture library (same as PHOTOLIBRARY for Android) + + private static final int JPEG = 0; // Take a picture of type JPEG + private static final int PNG = 1; // Take a picture of type PNG + + 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 + public String callbackId; + /** * Constructor. */ - public CameraLauncher() { - } + public CameraLauncher() { + } - /** - * 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 = ""; - this.callbackId = callbackId; - - 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); - } + /** + * 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 = ""; + this.callbackId = callbackId; + + 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 encodingType = JPEG; + this.encodingType = JPEG; if (args.length() > 5) { - encodingType = args.getInt(5); + this.encodingType = args.getInt(5); } - if (srcType == CAMERA) { - this.takePicture(args.getInt(0), destType, encodingType); - } - else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { - this.getImage(args.getInt(0), srcType, destType); - } - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - return r; - } - return new PluginResult(status, result); - } catch (JSONException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } - } - + if (srcType == CAMERA) { + this.takePicture(args.getInt(0), destType, encodingType); + } + else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { + this.getImage(args.getInt(0), srcType, destType); + } + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + } + return new PluginResult(status, result); + } catch (JSONException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- - /** - * Take a picture with the camera. - * When an image is captured or the camera view is cancelled, the result is returned - * in PhonegapActivity.onActivityResult, which forwards the result to this.onActivityResult. - * - * The image can either be returned as a base64 string or a URI that points to the file. - * To display base64 string in an img tag, set the source to: - * img.src="data:image/jpeg;base64,"+result; - * or to display URI in an img tag - * img.src=result; - * - * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - * @param returnType Set the type of image to return. - */ - public void takePicture(int quality, int returnType, int encodingType) { - this.mQuality = quality; - - // Display camera + /** + * Take a picture with the camera. + * When an image is captured or the camera view is cancelled, the result is returned + * in PhonegapActivity.onActivityResult, which forwards the result to this.onActivityResult. + * + * The image can either be returned as a base64 string or a URI that points to the file. + * To display base64 string in an img tag, set the source to: + * img.src="data:image/jpeg;base64,"+result; + * or to display URI in an img tag + * img.src=result; + * + * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + * @param returnType Set the type of image to return. + */ + public void takePicture(int quality, int returnType, int encodingType) { + this.mQuality = quality; + + // Display camera Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); // Specify file so that large image is captured and returned @@ -138,14 +140,14 @@ public class CameraLauncher extends Plugin { this.imageUri = Uri.fromFile(photo); this.ctx.startActivityForResult((Plugin) this, intent, (CAMERA+1)*16 + returnType+1); - } + } - /** - * Create a file in the applications temporary directory based upon the supplied encoding. - * - * @param encodingType of the image to be taken - * @return a File object pointing to the temporary picture - */ + /** + * Create a file in the applications temporary directory based upon the supplied encoding. + * + * @param encodingType of the image to be taken + * @return a File object pointing to the temporary picture + */ private File createCaptureFile(int encodingType) { File photo = null; if (encodingType == JPEG) { @@ -157,46 +159,46 @@ public class CameraLauncher extends Plugin { } /** - * Get image from photo library. - * - * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - * @param srcType The album to get image from. - * @param returnType Set the type of image to return. - */ - // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! - public void getImage(int quality, int srcType, int returnType) { - this.mQuality = quality; + * Get image from photo library. + * + * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + * @param srcType The album to get image from. + * @param returnType Set the type of image to return. + */ + // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! + public void getImage(int quality, int srcType, int returnType) { + this.mQuality = quality; - Intent intent = new Intent(); - intent.setType("image/*"); - 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); - } + Intent intent = new Intent(); + intent.setType("image/*"); + 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); + } - /** - * Scales the bitmap according to the requested size. - * - * @param bitmap The bitmap to scale. - * @return Bitmap A new Bitmap object of the same bitmap after scaling. - */ - public Bitmap scaleBitmap(Bitmap bitmap) { + /** + * Scales the bitmap according to the requested size. + * + * @param bitmap The bitmap to scale. + * @return Bitmap A new Bitmap object of the same bitmap after scaling. + */ + public Bitmap scaleBitmap(Bitmap bitmap) { int newWidth = this.targetWidth; int newHeight = this.targetHeight; int origWidth = bitmap.getWidth(); int origHeight = bitmap.getHeight(); // If no new width or height were specified return the original bitmap - if (newWidth <= 0 && newHeight <= 0) { - return bitmap; - } - // Only the width was specified - else if (newWidth > 0 && newHeight <= 0) { + if (newWidth <= 0 && newHeight <= 0) { + return bitmap; + } + // Only the width was specified + else if (newWidth > 0 && newHeight <= 0) { newHeight = (newWidth * origHeight) / origWidth; } - // only the height was specified - else if (newWidth <= 0 && newHeight > 0) { + // only the height was specified + else if (newWidth <= 0 && newHeight > 0) { newWidth = (newHeight * origWidth) / origHeight; } // If the user specified both a positive width and height @@ -205,7 +207,7 @@ public class CameraLauncher extends Plugin { // Alternatively, the specified width and height could have been // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this // would result in whitespace in the new image. - else { + else { double newRatio = newWidth / (double)newHeight; double origRatio = origWidth / (double)origHeight; @@ -217,156 +219,169 @@ public class CameraLauncher extends Plugin { } return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); - } - + } + /** * Called when the camera view exits. * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). */ - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - - // Get src and dest types from request code - int srcType = (requestCode/16) - 1; - int destType = (requestCode % 16) - 1; + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + + // Get src and dest types from request code + int srcType = (requestCode/16) - 1; + int destType = (requestCode % 16) - 1; - // If CAMERA - if (srcType == CAMERA) { - // If image available - if (resultCode == Activity.RESULT_OK) { - try { - // Read in bitmap of captured image - Bitmap bitmap; - try { - bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); - } catch (FileNotFoundException e) { - Uri uri = intent.getData(); - android.content.ContentResolver resolver = this.ctx.getContentResolver(); - bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); - } + // If CAMERA + if (srcType == CAMERA) { + // If image available + if (resultCode == Activity.RESULT_OK) { + try { + // Create an ExifHelper to save the exif data that is lost during compression + ExifHelper exif = new ExifHelper(); + if (this.encodingType == JPEG) { + exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx) + "/Pic.jpg"); + exif.readExifData(); + } - bitmap = scaleBitmap(bitmap); - - // If sending base64 image back - if (destType == DATA_URL) { - this.processPicture(bitmap); - } + // Read in bitmap of captured image + Bitmap bitmap; + try { + bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + } catch (FileNotFoundException e) { + Uri uri = intent.getData(); + android.content.ContentResolver resolver = this.ctx.getContentResolver(); + bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); + } - // If sending filename back - else if (destType == FILE_URI){ - // Create entry in media store for image - // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) - ContentValues values = new ContentValues(); - values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); - Uri uri = null; - try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - } catch (UnsupportedOperationException e) { - System.out.println("Can't write to external media storage."); - try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); - } catch (UnsupportedOperationException ex) { - System.out.println("Can't write to internal media storage."); - this.failPicture("Error capturing image - no media storage found."); - return; - } - } + bitmap = scaleBitmap(bitmap); + + // If sending base64 image back + if (destType == DATA_URL) { + this.processPicture(bitmap); + } - // Add compressed version of captured image to returned media store Uri - OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); - bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); - os.close(); + // If sending filename back + else if (destType == FILE_URI){ + // Create entry in media store for image + // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) + ContentValues values = new ContentValues(); + values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); + Uri uri = null; + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException e) { + System.out.println("Can't write to external media storage."); + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException ex) { + System.out.println("Can't write to internal media storage."); + this.failPicture("Error capturing image - no media storage found."); + return; + } + } - // Send Uri back to JavaScript for viewing image - this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); - } - bitmap.recycle(); - bitmap = null; - System.gc(); - } catch (IOException e) { - e.printStackTrace(); - this.failPicture("Error capturing image."); - } - } + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); + os.close(); + + // Restore exif data to file + if (this.encodingType == JPEG) { + exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx)); + exif.writeExifData(); + } - // If cancelled - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Camera cancelled."); - } + // Send Uri back to JavaScript for viewing image + this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); + } + bitmap.recycle(); + bitmap = null; + System.gc(); + } catch (IOException e) { + e.printStackTrace(); + this.failPicture("Error capturing image."); + } + } - // If something else - else { - this.failPicture("Did not complete!"); - } - } - - // If retrieving photo from library - else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { - if (resultCode == Activity.RESULT_OK) { - Uri uri = intent.getData(); - 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); - } - } - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Selection cancelled."); - } - else { - this.failPicture("Selection did not complete!"); - } - } - } - - /** - * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript. - * - * @param bitmap - */ - public void processPicture(Bitmap bitmap) { - ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); - try { - if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) { - byte[] code = jpeg_data.toByteArray(); - byte[] output = Base64.encodeBase64(code); - String js_out = new String(output); - this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId); - js_out = null; - output = null; - code = null; - } - } - catch(Exception e) { - this.failPicture("Error compressing image."); - } - jpeg_data = null; - } - - /** - * Send error message to JavaScript. - * - * @param err - */ - public void failPicture(String err) { - this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); - } -} + // If cancelled + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Camera cancelled."); + } + + // If something else + else { + this.failPicture("Did not complete!"); + } + } + + // If retrieving photo from library + else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { + if (resultCode == Activity.RESULT_OK) { + Uri uri = intent.getData(); + 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); + } + } + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Selection cancelled."); + } + else { + this.failPicture("Selection did not complete!"); + } + } + } + + /** + * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript. + * + * @param bitmap + */ + public void processPicture(Bitmap bitmap) { + ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); + try { + if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) { + byte[] code = jpeg_data.toByteArray(); + byte[] output = Base64.encodeBase64(code); + String js_out = new String(output); + this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId); + js_out = null; + output = null; + code = null; + } + } + catch(Exception e) { + this.failPicture("Error compressing image."); + } + jpeg_data = null; + } + + /** + * Send error message to JavaScript. + * + * @param err + */ + public void failPicture(String err) { + this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/Capture.java b/framework/src/com/phonegap/Capture.java index b078e5df..e9261442 100644 --- a/framework/src/com/phonegap/Capture.java +++ b/framework/src/com/phonegap/Capture.java @@ -18,12 +18,10 @@ import org.json.JSONObject; import android.app.Activity; import android.content.ContentValues; import android.content.Intent; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaPlayer; import android.net.Uri; -import android.os.Environment; import android.util.Log; import com.phonegap.api.Plugin; @@ -31,146 +29,145 @@ import com.phonegap.api.PluginResult; public class Capture extends Plugin { - private static final String _DATA = "_data"; // The column name where the file path is stored - private static final int CAPTURE_AUDIO = 0; // Constant for capture audio - private static final int CAPTURE_IMAGE = 1; // Constant for capture image - private static final int CAPTURE_VIDEO = 2; // Constant for capture video - private static final String LOG_TAG = "Capture"; - private String callbackId; // The ID of the callback to be invoked with our result - private long limit; // the number of pics/vids/clips to take - private double duration; // optional duration parameter for video recording - private JSONArray results; // The array of results to be returned to the user - private Uri imageUri; // Uri of captured image + private static final int CAPTURE_AUDIO = 0; // Constant for capture audio + private static final int CAPTURE_IMAGE = 1; // Constant for capture image + private static final int CAPTURE_VIDEO = 2; // Constant for capture video + private static final String LOG_TAG = "Capture"; + private String callbackId; // The ID of the callback to be invoked with our result + private long limit; // the number of pics/vids/clips to take + private double duration; // optional duration parameter for video recording + private JSONArray results; // The array of results to be returned to the user + private Uri imageUri; // Uri of captured image - @Override - public PluginResult execute(String action, JSONArray args, String callbackId) { - this.callbackId = callbackId; - this.limit = 1; - this.duration = 0.0f; - this.results = new JSONArray(); - - JSONObject options = args.optJSONObject(0); - if (options != null) { - limit = options.optLong("limit", 1); - duration = options.optDouble("duration", 0.0f); - } + @Override + public PluginResult execute(String action, JSONArray args, String callbackId) { + this.callbackId = callbackId; + this.limit = 1; + this.duration = 0.0f; + this.results = new JSONArray(); + + JSONObject options = args.optJSONObject(0); + if (options != null) { + limit = options.optLong("limit", 1); + duration = options.optDouble("duration", 0.0f); + } - if (action.equals("getFormatData")) { - try { - JSONObject obj = getFormatData(args.getString(0), args.getString(1)); - return new PluginResult(PluginResult.Status.OK, obj); - } catch (JSONException e) { - return new PluginResult(PluginResult.Status.ERROR); - } - } - else if (action.equals("captureAudio")) { - this.captureAudio(); - } - else if (action.equals("captureImage")) { - this.captureImage(); - } - else if (action.equals("captureVideo")) { - this.captureVideo(duration); - } - - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - return r; - } + if (action.equals("getFormatData")) { + try { + JSONObject obj = getFormatData(args.getString(0), args.getString(1)); + return new PluginResult(PluginResult.Status.OK, obj); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.ERROR); + } + } + else if (action.equals("captureAudio")) { + this.captureAudio(); + } + else if (action.equals("captureImage")) { + this.captureImage(); + } + else if (action.equals("captureVideo")) { + this.captureVideo(duration); + } + + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + } - /** - * Provides the media data file data depending on it's mime type - * - * @param filePath path to the file - * @param mimeType of the file - * @return a MediaFileData object - */ - private JSONObject getFormatData(String filePath, String mimeType) { - JSONObject obj = new JSONObject(); - try { - // setup defaults - obj.put("height", 0); - obj.put("width", 0); - obj.put("bitrate", 0); - obj.put("duration", 0); - obj.put("codecs", ""); + /** + * Provides the media data file data depending on it's mime type + * + * @param filePath path to the file + * @param mimeType of the file + * @return a MediaFileData object + */ + private JSONObject getFormatData(String filePath, String mimeType) { + JSONObject obj = new JSONObject(); + try { + // setup defaults + obj.put("height", 0); + obj.put("width", 0); + obj.put("bitrate", 0); + obj.put("duration", 0); + obj.put("codecs", ""); - // If the mimeType isn't set the rest will fail - // so let's see if we can determine it. - if (mimeType == null || mimeType.equals("")) { - mimeType = FileUtils.getMimeType(filePath); - } - - if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) { - obj = getImageData(filePath, obj); - } - else if (filePath.endsWith("audio/3gpp")) { - obj = getAudioVideoData(filePath, obj, false); - } - else if (mimeType.equals("video/3gpp")) { - obj = getAudioVideoData(filePath, obj, true); - } - } - catch (JSONException e) { - Log.d(LOG_TAG, "Error: setting media file data object"); - } - return obj; - } + // If the mimeType isn't set the rest will fail + // so let's see if we can determine it. + if (mimeType == null || mimeType.equals("")) { + mimeType = FileUtils.getMimeType(filePath); + } + + if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) { + obj = getImageData(filePath, obj); + } + else if (filePath.endsWith("audio/3gpp")) { + obj = getAudioVideoData(filePath, obj, false); + } + else if (mimeType.equals("video/3gpp")) { + obj = getAudioVideoData(filePath, obj, true); + } + } + catch (JSONException e) { + Log.d(LOG_TAG, "Error: setting media file data object"); + } + return obj; + } - /** - * Get the Image specific attributes - * - * @param filePath path to the file - * @param obj represents the Media File Data - * @return a JSONObject that represents the Media File Data - * @throws JSONException - */ - private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException { - Bitmap bitmap = BitmapFactory.decodeFile(filePath); - obj.put("height", bitmap.getHeight()); - obj.put("width", bitmap.getWidth()); - return obj; - } + /** + * Get the Image specific attributes + * + * @param filePath path to the file + * @param obj represents the Media File Data + * @return a JSONObject that represents the Media File Data + * @throws JSONException + */ + private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException { + Bitmap bitmap = BitmapFactory.decodeFile(filePath); + obj.put("height", bitmap.getHeight()); + obj.put("width", bitmap.getWidth()); + return obj; + } - /** - * Get the Image specific attributes - * - * @param filePath path to the file - * @param obj represents the Media File Data - * @param video if true get video attributes as well - * @return a JSONObject that represents the Media File Data - * @throws JSONException - */ - private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException { - MediaPlayer player = new MediaPlayer(); - try { - player.setDataSource(filePath); - player.prepare(); - obj.put("duration", player.getDuration()); - if (video) { - obj.put("height", player.getVideoHeight()); - obj.put("width", player.getVideoWidth()); - } - } - catch (IOException e) { - Log.d(LOG_TAG, "Error: loading video file"); - } - return obj; - } + /** + * Get the Image specific attributes + * + * @param filePath path to the file + * @param obj represents the Media File Data + * @param video if true get video attributes as well + * @return a JSONObject that represents the Media File Data + * @throws JSONException + */ + private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException { + MediaPlayer player = new MediaPlayer(); + try { + player.setDataSource(filePath); + player.prepare(); + obj.put("duration", player.getDuration()); + if (video) { + obj.put("height", player.getVideoHeight()); + obj.put("width", player.getVideoWidth()); + } + } + catch (IOException e) { + Log.d(LOG_TAG, "Error: loading video file"); + } + return obj; + } - /** - * Sets up an intent to capture audio. Result handled by onActivityResult() - */ - private void captureAudio() { + /** + * Sets up an intent to capture audio. Result handled by onActivityResult() + */ + private void captureAudio() { Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_AUDIO); } - /** - * Sets up an intent to capture images. Result handled by onActivityResult() - */ - private void captureImage() { + /** + * Sets up an intent to capture images. Result handled by onActivityResult() + */ + private void captureImage() { Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); // Specify file so that large image is captured and returned @@ -181,179 +178,174 @@ public class Capture extends Plugin { this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_IMAGE); } - /** - * Sets up an intent to capture video. Result handled by onActivityResult() - */ - private void captureVideo(double duration) { + /** + * Sets up an intent to capture video. Result handled by onActivityResult() + */ + private void captureVideo(double duration) { Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); // Introduced in API 8 //intent.putExtra(android.provider.MediaStore.EXTRA_DURATION_LIMIT, duration); this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_VIDEO); } - + /** * Called when the video view exits. * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). * @throws JSONException */ - public void onActivityResult(int requestCode, int resultCode, Intent intent) { + public void onActivityResult(int requestCode, int resultCode, Intent intent) { - // Result received okay - if (resultCode == Activity.RESULT_OK) { - // An audio clip was requested - if (requestCode == CAPTURE_AUDIO) { - // Get the uri of the audio clip - Uri data = intent.getData(); - // create a file object from the uri - results.put(createMediaFile(data)); + // Result received okay + if (resultCode == Activity.RESULT_OK) { + // An audio clip was requested + if (requestCode == CAPTURE_AUDIO) { + // Get the uri of the audio clip + Uri data = intent.getData(); + // create a file object from the uri + results.put(createMediaFile(data)); - if (results.length() >= limit) { - // Send Uri back to JavaScript for listening to audio - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more audio clips - captureAudio(); - } - } else if (requestCode == CAPTURE_IMAGE) { - // For some reason if I try to do: - // Uri data = intent.getData(); - // It crashes in the emulator and on my phone with a null pointer exception - // To work around it I had to grab the code from CameraLauncher.java - try { - // Read in bitmap of captured image - Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + if (results.length() >= limit) { + // Send Uri back to JavaScript for listening to audio + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more audio clips + captureAudio(); + } + } else if (requestCode == CAPTURE_IMAGE) { + // For some reason if I try to do: + // Uri data = intent.getData(); + // It crashes in the emulator and on my phone with a null pointer exception + // To work around it I had to grab the code from CameraLauncher.java + try { + // Create an ExifHelper to save the exif data that is lost during compression + ExifHelper exif = new ExifHelper(); + exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx) + "/Capture.jpg"); + exif.readExifData(); + + // Read in bitmap of captured image + Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); - // Create entry in media store for image - // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) - ContentValues values = new ContentValues(); - values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); - Uri uri = null; - try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - } catch (UnsupportedOperationException e) { - System.out.println("Can't write to external media storage."); - try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); - } catch (UnsupportedOperationException ex) { - System.out.println("Can't write to internal media storage."); - this.fail("Error capturing image - no media storage found."); - return; - } - } + // Create entry in media store for image + // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) + ContentValues values = new ContentValues(); + values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); + Uri uri = null; + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException e) { + System.out.println("Can't write to external media storage."); + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException ex) { + System.out.println("Can't write to internal media storage."); + this.fail("Error capturing image - no media storage found."); + return; + } + } - // Add compressed version of captured image to returned media store Uri - OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); - bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); - os.close(); + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.close(); - bitmap.recycle(); - bitmap = null; - System.gc(); - - // Add image to results - results.put(createMediaFile(uri)); - - if (results.length() >= limit) { - // Send Uri back to JavaScript for viewing image - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more images - captureImage(); - } - } catch (IOException e) { - e.printStackTrace(); - this.fail("Error capturing image."); - } - } else if (requestCode == CAPTURE_VIDEO) { - // Get the uri of the video clip - Uri data = intent.getData(); - // create a file object from the uri - results.put(createMediaFile(data)); + bitmap.recycle(); + bitmap = null; + System.gc(); + + // Restore exif data to file + exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx)); + exif.writeExifData(); + + // Add image to results + results.put(createMediaFile(uri)); + + if (results.length() >= limit) { + // Send Uri back to JavaScript for viewing image + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more images + captureImage(); + } + } catch (IOException e) { + e.printStackTrace(); + this.fail("Error capturing image."); + } + } else if (requestCode == CAPTURE_VIDEO) { + // Get the uri of the video clip + Uri data = intent.getData(); + // create a file object from the uri + results.put(createMediaFile(data)); - if (results.length() >= limit) { - // Send Uri back to JavaScript for viewing video - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more video clips - captureVideo(duration); - } - } - } - // If canceled - else if (resultCode == Activity.RESULT_CANCELED) { - // If we have partial results send them back to the user - if (results.length() > 0) { - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } - // user canceled the action - else { - this.fail("Canceled."); - } - } - // If something else - else { - // If we have partial results send them back to the user - if (results.length() > 0) { - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } - // something bad happened - else { - this.fail("Did not complete!"); - } - } - } - - /** - * Creates a JSONObject that represents a File from the Uri - * - * @param data the Uri of the audio/image/video - * @return a JSONObject that represents a File - */ - private JSONObject createMediaFile(Uri data) { - File fp = new File(getRealPathFromURI(data)); - - JSONObject obj = new JSONObject(); - - try { - // File properties - obj.put("name", fp.getName()); - obj.put("fullPath", fp.getAbsolutePath()); - obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); - obj.put("lastModifiedDate", fp.lastModified()); - obj.put("size", fp.length()); - } catch (JSONException e) { - // this will never happen - e.printStackTrace(); - } - - return obj; - } - - /** - * Queries the media store to find out what the file path is for the Uri we supply - * - * @param contentUri the Uri of the audio/image/video - * @return the full path to the file - */ - private String getRealPathFromURI(Uri contentUri) { - String[] proj = { _DATA }; - Cursor cursor = this.ctx.managedQuery(contentUri, proj, null, null, null); - int column_index = cursor.getColumnIndexOrThrow(_DATA); - cursor.moveToFirst(); - return cursor.getString(column_index); + if (results.length() >= limit) { + // Send Uri back to JavaScript for viewing video + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more video clips + captureVideo(duration); + } + } + } + // If canceled + else if (resultCode == Activity.RESULT_CANCELED) { + // If we have partial results send them back to the user + if (results.length() > 0) { + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } + // user canceled the action + else { + this.fail("Canceled."); + } + } + // If something else + else { + // If we have partial results send them back to the user + if (results.length() > 0) { + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } + // something bad happened + else { + this.fail("Did not complete!"); + } + } } - /** - * Send error message to JavaScript. - * - * @param err - */ - public void fail(String err) { - this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); - } + /** + * Creates a JSONObject that represents a File from the Uri + * + * @param data the Uri of the audio/image/video + * @return a JSONObject that represents a File + * @throws IOException + */ + private JSONObject createMediaFile(Uri data){ + File fp = new File(FileUtils.getRealPathFromURI(data, this.ctx)); + JSONObject obj = new JSONObject(); + + try { + // File properties + obj.put("name", fp.getName()); + obj.put("fullPath", fp.getAbsolutePath()); + obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); + obj.put("lastModifiedDate", fp.lastModified()); + obj.put("size", fp.length()); + } catch (JSONException e) { + // this will never happen + e.printStackTrace(); + } + + return obj; + } + + /** + * Send error message to JavaScript. + * + * @param err + */ + public void fail(String err) { + this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); + } } \ No newline at end of file diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 559be52e..fc210341 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -91,90 +91,90 @@ import com.phonegap.api.PluginManager; * * Properties: The application can be configured using the following properties: * - * // Display a native loading dialog. Format for value = "Title,Message". - * // (String - default=null) - * super.setStringProperty("loadingDialog", "Wait,Loading Demo..."); + * // Display a native loading dialog. Format for value = "Title,Message". + * // (String - default=null) + * super.setStringProperty("loadingDialog", "Wait,Loading Demo..."); * - * // Display a native loading dialog when loading sub-pages. Format for value = "Title,Message". - * // (String - default=null) - * super.setStringProperty("loadingPageDialog", "Loading page..."); + * // Display a native loading dialog when loading sub-pages. Format for value = "Title,Message". + * // (String - default=null) + * super.setStringProperty("loadingPageDialog", "Loading page..."); * - * // Cause all links on web page to be loaded into existing web view, - * // instead of being loaded into new browser. (Boolean - default=false) - * super.setBooleanProperty("loadInWebView", true); + * // Cause all links on web page to be loaded into existing web view, + * // instead of being loaded into new browser. (Boolean - default=false) + * super.setBooleanProperty("loadInWebView", true); * - * // Load a splash screen image from the resource drawable directory. - * // (Integer - default=0) - * super.setIntegerProperty("splashscreen", R.drawable.splash); + * // Load a splash screen image from the resource drawable directory. + * // (Integer - default=0) + * super.setIntegerProperty("splashscreen", R.drawable.splash); * - * // Set the background color. - * // (Integer - default=0 or BLACK) - * super.setIntegerProperty("backgroundColor", Color.WHITE); + * // Set the background color. + * // (Integer - default=0 or BLACK) + * super.setIntegerProperty("backgroundColor", Color.WHITE); * - * // Time in msec to wait before triggering a timeout error when loading - * // with super.loadUrl(). (Integer - default=20000) - * super.setIntegerProperty("loadUrlTimeoutValue", 60000); + * // Time in msec to wait before triggering a timeout error when loading + * // with super.loadUrl(). (Integer - default=20000) + * super.setIntegerProperty("loadUrlTimeoutValue", 60000); * - * // URL to load if there's an error loading specified URL with loadUrl(). - * // Should be a local URL starting with file://. (String - default=null) - * super.setStringProperty("errorUrl", "file:///android_asset/www/error.html"); + * // URL to load if there's an error loading specified URL with loadUrl(). + * // Should be a local URL starting with file://. (String - default=null) + * super.setStringProperty("errorUrl", "file:///android_asset/www/error.html"); * - * // Enable app to keep running in background. (Boolean - default=true) - * super.setBooleanProperty("keepRunning", false); + * // Enable app to keep running in background. (Boolean - default=true) + * super.setBooleanProperty("keepRunning", false); */ public class DroidGap extends PhonegapActivity { - // The webview for our app - protected WebView appView; - protected WebViewClient webViewClient; + // The webview for our app + protected WebView appView; + protected WebViewClient webViewClient; - protected LinearLayout root; - public boolean bound = false; - public CallbackServer callbackServer; - protected PluginManager pluginManager; - protected boolean cancelLoadUrl = false; - protected boolean clearHistory = false; - protected ProgressDialog spinnerDialog = null; + protected LinearLayout root; + public boolean bound = false; + public CallbackServer callbackServer; + protected PluginManager pluginManager; + protected boolean cancelLoadUrl = false; + protected boolean clearHistory = false; + protected ProgressDialog spinnerDialog = null; - // The initial URL for our app - // ie http://server/path/index.html#abc?query - private String url; - - // The base of the initial URL for our app. - // Does not include file name. Ends with / - // ie http://server/path/ - private String baseUrl = null; + // The initial URL for our app + // ie http://server/path/index.html#abc?query + private String url; + + // The base of the initial URL for our app. + // Does not include file name. Ends with / + // ie http://server/path/ + private String baseUrl = null; - // Plugin to call when activity result is received - protected IPlugin activityResultCallback = null; - protected boolean activityResultKeepRunning; + // Plugin to call when activity result is received + protected IPlugin activityResultCallback = null; + protected boolean activityResultKeepRunning; - // Flag indicates that a loadUrl timeout occurred - private int loadUrlTimeout = 0; - - // Default background color for activity - // (this is not the color for the webview, which is set in HTML) - private int backgroundColor = Color.BLACK; - - /* - * The variables below are used to cache some of the activity properties. - */ + // Flag indicates that a loadUrl timeout occurred + private int loadUrlTimeout = 0; + + // Default background color for activity + // (this is not the color for the webview, which is set in HTML) + private int backgroundColor = Color.BLACK; + + /* + * The variables below are used to cache some of the activity properties. + */ - // Flag indicates that a URL navigated to from PhoneGap app should be loaded into same webview - // instead of being loaded into the web browser. - protected boolean loadInWebView = false; + // Flag indicates that a URL navigated to from PhoneGap app should be loaded into same webview + // instead of being loaded into the web browser. + protected boolean loadInWebView = false; - // Draw a splash screen using an image located in the drawable resource directory. - // This is not the same as calling super.loadSplashscreen(url) - protected int splashscreen = 0; + // Draw a splash screen using an image located in the drawable resource directory. + // This is not the same as calling super.loadSplashscreen(url) + protected int splashscreen = 0; - // LoadUrl timeout value in msec (default of 20 sec) - protected int loadUrlTimeoutValue = 20000; - - // Keep app running when pause is received. (default = true) - // If true, then the JavaScript and native code continue to run in the background - // when another application (activity) is started. - protected boolean keepRunning = true; + // LoadUrl timeout value in msec (default of 20 sec) + protected int loadUrlTimeoutValue = 20000; + + // Keep app running when pause is received. (default = true) + // If true, then the JavaScript and native code continue to run in the background + // when another application (activity) is started. + protected boolean keepRunning = true; /** * Called when the activity is first created. @@ -183,55 +183,55 @@ public class DroidGap extends PhonegapActivity { */ @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - // This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket! + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + // This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket! - Display display = getWindowManager().getDefaultDisplay(); - int width = display.getWidth(); - int height = display.getHeight(); - - root = new LinearLayoutSoftKeyboardDetect(this, width, height); - root.setOrientation(LinearLayout.VERTICAL); - root.setBackgroundColor(this.backgroundColor); - root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT, 0.0F)); + Display display = getWindowManager().getDefaultDisplay(); + int width = display.getWidth(); + int height = display.getHeight(); + + root = new LinearLayoutSoftKeyboardDetect(this, width, height); + root.setOrientation(LinearLayout.VERTICAL); + root.setBackgroundColor(this.backgroundColor); + root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT, 0.0F)); - // If url was passed in to intent, then init webview, which will load the url - Bundle bundle = this.getIntent().getExtras(); - if (bundle != null) { - String url = bundle.getString("url"); - if (url != null) { - this.init(); - } - } - // Setup the hardware volume controls to handle volume control - setVolumeControlStream(AudioManager.STREAM_MUSIC); + // If url was passed in to intent, then init webview, which will load the url + Bundle bundle = this.getIntent().getExtras(); + if (bundle != null) { + String url = bundle.getString("url"); + if (url != null) { + this.init(); + } + } + // Setup the hardware volume controls to handle volume control + setVolumeControlStream(AudioManager.STREAM_MUSIC); } /** * Create and initialize web container. */ - public void init() { - - // Create web container - this.appView = new WebView(DroidGap.this); - this.appView.setId(100); - - this.appView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT, - 1.0F)); + public void init() { + + // Create web container + this.appView = new WebView(DroidGap.this); + this.appView.setId(100); + + this.appView.setLayoutParams(new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT, + 1.0F)); WebViewReflect.checkCompatibility(); if (android.os.Build.VERSION.RELEASE.startsWith("1.")) { - this.appView.setWebChromeClient(new GapClient(DroidGap.this)); + this.appView.setWebChromeClient(new GapClient(DroidGap.this)); } else { - this.appView.setWebChromeClient(new EclairClient(DroidGap.this)); + this.appView.setWebChromeClient(new EclairClient(DroidGap.this)); } this.setWebViewClient(this.appView, new GapViewClient(this)); @@ -271,209 +271,209 @@ public class DroidGap extends PhonegapActivity { // If url specified, then load it String url = this.getStringProperty("url", null); if (url != null) { - System.out.println("Loading initial URL="+url); - this.loadUrl(url); + System.out.println("Loading initial URL="+url); + this.loadUrl(url); } - } - - /** - * Set the WebViewClient. - * - * @param appView - * @param client - */ - protected void setWebViewClient(WebView appView, WebViewClient client) { - this.webViewClient = client; - appView.setWebViewClient(client); - } + } + + /** + * Set the WebViewClient. + * + * @param appView + * @param client + */ + protected void setWebViewClient(WebView appView, WebViewClient client) { + this.webViewClient = client; + appView.setWebViewClient(client); + } /** * Bind PhoneGap objects to JavaScript. * * @param appView */ - private void bindBrowser(WebView appView) { - this.callbackServer = new CallbackServer(); - this.pluginManager = new PluginManager(appView, this); + private void bindBrowser(WebView appView) { + this.callbackServer = new CallbackServer(); + this.pluginManager = new PluginManager(appView, this); - } + } - /** - * Look at activity parameters and process them. - * This must be called from the main UI thread. - */ - private void handleActivityParameters() { + /** + * Look at activity parameters and process them. + * This must be called from the main UI thread. + */ + private void handleActivityParameters() { - // Init web view if not already done - if (this.appView == null) { - this.init(); - } + // Init web view if not already done + if (this.appView == null) { + this.init(); + } - // If backgroundColor - this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); - this.root.setBackgroundColor(this.backgroundColor); + // If backgroundColor + this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); + this.root.setBackgroundColor(this.backgroundColor); - // If spashscreen - this.splashscreen = this.getIntegerProperty("splashscreen", 0); - if (this.splashscreen != 0) { - root.setBackgroundResource(this.splashscreen); - } + // If spashscreen + this.splashscreen = this.getIntegerProperty("splashscreen", 0); + if (this.splashscreen != 0) { + root.setBackgroundResource(this.splashscreen); + } - // If loadInWebView - this.loadInWebView = this.getBooleanProperty("loadInWebView", false); + // If loadInWebView + this.loadInWebView = this.getBooleanProperty("loadInWebView", false); - // If loadUrlTimeoutValue - int timeout = this.getIntegerProperty("loadUrlTimeoutValue", 0); - if (timeout > 0) { - this.loadUrlTimeoutValue = timeout; - } - - // If keepRunning - this.keepRunning = this.getBooleanProperty("keepRunning", true); - } - + // If loadUrlTimeoutValue + int timeout = this.getIntegerProperty("loadUrlTimeoutValue", 0); + if (timeout > 0) { + this.loadUrlTimeoutValue = timeout; + } + + // If keepRunning + this.keepRunning = this.getBooleanProperty("keepRunning", true); + } + /** * Load the url into the webview. * * @param url */ - public void loadUrl(final String url) { - System.out.println("loadUrl("+url+")"); - this.url = url; - if (this.baseUrl == null) { - int i = url.lastIndexOf('/'); - if (i > 0) { - this.baseUrl = url.substring(0, i+1); - } - else { - this.baseUrl = this.url + "/"; - } - } - System.out.println("url="+url+" baseUrl="+baseUrl); + public void loadUrl(final String url) { + System.out.println("loadUrl("+url+")"); + this.url = url; + if (this.baseUrl == null) { + int i = url.lastIndexOf('/'); + if (i > 0) { + this.baseUrl = url.substring(0, i+1); + } + else { + this.baseUrl = this.url + "/"; + } + } + System.out.println("url="+url+" baseUrl="+baseUrl); - // Load URL on UI thread - final DroidGap me = this; - this.runOnUiThread(new Runnable() { - public void run() { + // Load URL on UI thread + final DroidGap me = this; + this.runOnUiThread(new Runnable() { + public void run() { - // Handle activity parameters - me.handleActivityParameters(); + // Handle activity parameters + me.handleActivityParameters(); - // Initialize callback server - me.callbackServer.init(url); + // Initialize callback server + me.callbackServer.init(url); - // If loadingDialog, then show the App loading dialog - String loading = me.getStringProperty("loadingDialog", null); - if (loading != null) { + // If loadingDialog, then show the App loading dialog + String loading = me.getStringProperty("loadingDialog", null); + if (loading != null) { - String title = ""; - String message = "Loading Application..."; + String title = ""; + String message = "Loading Application..."; - if (loading.length() > 0) { - int comma = loading.indexOf(','); - if (comma > 0) { - title = loading.substring(0, comma); - message = loading.substring(comma+1); - } - else { - title = ""; - message = loading; - } - } - me.spinnerStart(title, message); - } + if (loading.length() > 0) { + int comma = loading.indexOf(','); + if (comma > 0) { + title = loading.substring(0, comma); + message = loading.substring(comma+1); + } + else { + title = ""; + message = loading; + } + } + me.spinnerStart(title, message); + } - // Create a timeout timer for loadUrl - final int currentLoadUrlTimeout = me.loadUrlTimeout; - Runnable runnable = new Runnable() { - public void run() { - try { - synchronized(this) { - wait(me.loadUrlTimeoutValue); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } + // Create a timeout timer for loadUrl + final int currentLoadUrlTimeout = me.loadUrlTimeout; + Runnable runnable = new Runnable() { + public void run() { + try { + synchronized(this) { + wait(me.loadUrlTimeoutValue); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } - // If timeout, then stop loading and handle error - if (me.loadUrlTimeout == currentLoadUrlTimeout) { - me.appView.stopLoading(); - me.webViewClient.onReceivedError(me.appView, -6, "The connection to the server was unsuccessful.", url); - } - } - }; - Thread thread = new Thread(runnable); - thread.start(); - me.appView.loadUrl(url); - } - }); - } - - /** - * Load the url into the webview after waiting for period of time. - * This is used to display the splashscreen for certain amount of time. - * - * @param url - * @param time The number of ms to wait before loading webview - */ - public void loadUrl(final String url, final int time) { - System.out.println("loadUrl("+url+","+time+")"); - final DroidGap me = this; + // If timeout, then stop loading and handle error + if (me.loadUrlTimeout == currentLoadUrlTimeout) { + me.appView.stopLoading(); + me.webViewClient.onReceivedError(me.appView, -6, "The connection to the server was unsuccessful.", url); + } + } + }; + Thread thread = new Thread(runnable); + thread.start(); + me.appView.loadUrl(url); + } + }); + } + + /** + * Load the url into the webview after waiting for period of time. + * This is used to display the splashscreen for certain amount of time. + * + * @param url + * @param time The number of ms to wait before loading webview + */ + public void loadUrl(final String url, final int time) { + System.out.println("loadUrl("+url+","+time+")"); + final DroidGap me = this; - // Handle activity parameters - this.runOnUiThread(new Runnable() { - public void run() { - me.handleActivityParameters(); - } - }); + // Handle activity parameters + this.runOnUiThread(new Runnable() { + public void run() { + me.handleActivityParameters(); + } + }); - Runnable runnable = new Runnable() { - public void run() { - try { - synchronized(this) { - this.wait(time); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - if (!me.cancelLoadUrl) { - me.loadUrl(url); - } - else{ - me.cancelLoadUrl = false; - System.out.println("Aborting loadUrl("+url+"): Another URL was loaded before timer expired."); - } - } - }; - Thread thread = new Thread(runnable); - thread.start(); - } - - /** - * Cancel loadUrl before it has been loaded. - */ - public void cancelLoadUrl() { - this.cancelLoadUrl = true; - } - - /** - * Clear the resource cache. - */ - public void clearCache() { - if (this.appView == null) { - this.init(); - } - this.appView.clearCache(true); - } + Runnable runnable = new Runnable() { + public void run() { + try { + synchronized(this) { + this.wait(time); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (!me.cancelLoadUrl) { + me.loadUrl(url); + } + else{ + me.cancelLoadUrl = false; + System.out.println("Aborting loadUrl("+url+"): Another URL was loaded before timer expired."); + } + } + }; + Thread thread = new Thread(runnable); + thread.start(); + } + + /** + * Cancel loadUrl before it has been loaded. + */ + public void cancelLoadUrl() { + this.cancelLoadUrl = true; + } + + /** + * Clear the resource cache. + */ + public void clearCache() { + if (this.appView == null) { + this.init(); + } + this.appView.clearCache(true); + } /** * Clear web history in this web view. */ public void clearHistory() { - this.clearHistory = true; - if (this.appView != null) { - this.appView.clearHistory(); - } + this.clearHistory = true; + if (this.appView != null) { + this.appView.clearHistory(); + } } @Override @@ -483,8 +483,8 @@ public class DroidGap extends PhonegapActivity { * @param Configuration newConfig */ public void onConfigurationChanged(Configuration newConfig) { - //don't reload the current page when the orientation is changed - super.onConfigurationChanged(newConfig); + //don't reload the current page when the orientation is changed + super.onConfigurationChanged(newConfig); } /** @@ -495,15 +495,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public boolean getBooleanProperty(String name, boolean defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Boolean p = (Boolean)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.booleanValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Boolean p = (Boolean)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.booleanValue(); } /** @@ -514,15 +514,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public int getIntegerProperty(String name, int defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Integer p = (Integer)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.intValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Integer p = (Integer)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.intValue(); } /** @@ -533,15 +533,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public String getStringProperty(String name, String defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - String p = bundle.getString(name); - if (p == null) { - return defaultValue; - } - return p; + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + String p = bundle.getString(name); + if (p == null) { + return defaultValue; + } + return p; } /** @@ -552,15 +552,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public double getDoubleProperty(String name, double defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Double p = (Double)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.doubleValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Double p = (Double)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.doubleValue(); } /** @@ -570,7 +570,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setBooleanProperty(String name, boolean value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -580,7 +580,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setIntegerProperty(String name, int value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -590,7 +590,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setStringProperty(String name, String value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -600,7 +600,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setDoubleProperty(String name, double value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } @Override @@ -610,20 +610,20 @@ public class DroidGap extends PhonegapActivity { protected void onPause() { super.onPause(); if (this.appView == null) { - return; + return; } - // Send pause event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); + // Send pause event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); - // Forward to plugins - this.pluginManager.onPause(this.keepRunning); + // Forward to plugins + this.pluginManager.onPause(this.keepRunning); // If app doesn't want to run in background if (!this.keepRunning) { - // Pause JavaScript timers (including setInterval) - this.appView.pauseTimers(); + // Pause JavaScript timers (including setInterval) + this.appView.pauseTimers(); } } @@ -632,10 +632,10 @@ public class DroidGap extends PhonegapActivity { * Called when the activity receives a new intent **/ protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); + super.onNewIntent(intent); - //Forward to plugins - this.pluginManager.onNewIntent(intent); + //Forward to plugins + this.pluginManager.onNewIntent(intent); } @Override @@ -645,26 +645,26 @@ public class DroidGap extends PhonegapActivity { protected void onResume() { super.onResume(); if (this.appView == null) { - return; + return; } - // Send resume event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};"); + // Send resume event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};"); - // Forward to plugins - this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); + // Forward to plugins + this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); // If app doesn't want to run in background if (!this.keepRunning || this.activityResultKeepRunning) { - // Restore multitasking state - if (this.activityResultKeepRunning) { - this.keepRunning = this.activityResultKeepRunning; - this.activityResultKeepRunning = false; - } + // Restore multitasking state + if (this.activityResultKeepRunning) { + this.keepRunning = this.activityResultKeepRunning; + this.activityResultKeepRunning = false; + } - // Resume JavaScript timers (including setInterval) - this.appView.resumeTimers(); + // Resume JavaScript timers (including setInterval) + this.appView.resumeTimers(); } } @@ -673,21 +673,21 @@ public class DroidGap extends PhonegapActivity { * The final call you receive before your activity is destroyed. */ public void onDestroy() { - super.onDestroy(); - + super.onDestroy(); + if (this.appView != null) { - - // Make sure pause event is sent if onPause hasn't been called before onDestroy - this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); + + // Make sure pause event is sent if onPause hasn't been called before onDestroy + this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); - // Send destroy event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};"); + // Send destroy event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};"); - // Load blank page so that JavaScript onunload is called - this.appView.loadUrl("about:blank"); - - // Forward to plugins - this.pluginManager.onDestroy(); + // Load blank page so that JavaScript onunload is called + this.appView.loadUrl("about:blank"); + + // Forward to plugins + this.pluginManager.onDestroy(); } } @@ -699,7 +699,7 @@ public class DroidGap extends PhonegapActivity { * @param className */ public void addService(String serviceType, String className) { - this.pluginManager.addService(serviceType, className); + this.pluginManager.addService(serviceType, className); } /** @@ -709,7 +709,7 @@ public class DroidGap extends PhonegapActivity { * @param message */ public void sendJavascript(String statement) { - this.callbackServer.sendJavascript(statement); + this.callbackServer.sendJavascript(statement); } @@ -721,85 +721,85 @@ public class DroidGap extends PhonegapActivity { * * @param url The url to load. * @param usePhoneGap Load url in PhoneGap webview. - * @param clearPrev Clear the activity stack, so new app becomes top of stack - * @param params DroidGap parameters for new app + * @param clearPrev Clear the activity stack, so new app becomes top of stack + * @param params DroidGap parameters for new app * @throws android.content.ActivityNotFoundException */ public void showWebPage(String url, boolean usePhoneGap, boolean clearPrev, HashMap params) throws android.content.ActivityNotFoundException { - Intent intent = null; - if (usePhoneGap) { - try { - intent = new Intent().setClass(this, Class.forName(this.getComponentName().getClassName())); - intent.putExtra("url", url); + Intent intent = null; + if (usePhoneGap) { + try { + intent = new Intent().setClass(this, Class.forName(this.getComponentName().getClassName())); + intent.putExtra("url", url); - // Add parameters - if (params != null) { - java.util.Set> s = params.entrySet(); - java.util.Iterator> it = s.iterator(); - while(it.hasNext()) { - Entry entry = it.next(); - String key = entry.getKey(); - Object value = entry.getValue(); - if (value == null) { - } - else if (value.getClass().equals(String.class)) { - intent.putExtra(key, (String)value); - } - else if (value.getClass().equals(Boolean.class)) { - intent.putExtra(key, (Boolean)value); - } - else if (value.getClass().equals(Integer.class)) { - intent.putExtra(key, (Integer)value); - } - } + // Add parameters + if (params != null) { + java.util.Set> s = params.entrySet(); + java.util.Iterator> it = s.iterator(); + while(it.hasNext()) { + Entry entry = it.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + } + else if (value.getClass().equals(String.class)) { + intent.putExtra(key, (String)value); + } + else if (value.getClass().equals(Boolean.class)) { + intent.putExtra(key, (Boolean)value); + } + else if (value.getClass().equals(Integer.class)) { + intent.putExtra(key, (Integer)value); + } + } - } - } catch (ClassNotFoundException e) { - e.printStackTrace(); - intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - } - } - else { - intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - } - this.startActivity(intent); - - // Finish current activity - if (clearPrev) { - this.finish(); - } + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + } + } + else { + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + } + this.startActivity(intent); + + // Finish current activity + if (clearPrev) { + this.finish(); + } } /** * Show the spinner. Must be called from the UI thread. * - * @param title Title of the dialog - * @param message The message of the dialog + * @param title Title of the dialog + * @param message The message of the dialog */ public void spinnerStart(final String title, final String message) { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } - final DroidGap me = this; - this.spinnerDialog = ProgressDialog.show(DroidGap.this, title , message, true, true, - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - me.spinnerDialog = null; - } - }); + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; + } + final DroidGap me = this; + this.spinnerDialog = ProgressDialog.show(DroidGap.this, title , message, true, true, + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + me.spinnerDialog = null; + } + }); } /** * Stop spinner. */ public void spinnerStop() { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; + } } /** @@ -834,11 +834,11 @@ public class DroidGap extends PhonegapActivity { dlg.setTitle("Alert"); dlg.setCancelable(false); dlg.setPositiveButton(android.R.string.ok, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.confirm(); - } - }); + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.confirm(); + } + }); dlg.create(); dlg.show(); return true; @@ -859,15 +859,15 @@ public class DroidGap extends PhonegapActivity { dlg.setTitle("Confirm"); dlg.setCancelable(false); dlg.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.confirm(); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.confirm(); } }); dlg.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.cancel(); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.cancel(); } }); dlg.create(); @@ -888,91 +888,91 @@ public class DroidGap extends PhonegapActivity { */ @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { - - // Security check to make sure any requests are coming from the page initially - // loaded in webview and not another loaded in an iframe. - boolean reqOk = false; - if (url.indexOf(this.ctx.baseUrl) == 0) { - reqOk = true; - } - - // Calling PluginManager.exec() to call a native service using - // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); - if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { - JSONArray array; - try { - array = new JSONArray(defaultValue.substring(4)); - String service = array.getString(0); - String action = array.getString(1); - String callbackId = array.getString(2); - boolean async = array.getBoolean(3); - String r = pluginManager.exec(service, action, callbackId, message, async); - result.confirm(r); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - // Polling for JavaScript messages - else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { - String r = callbackServer.getJavascript(); - result.confirm(r); - } - - // Calling into CallbackServer - else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { - String r = ""; - if (message.equals("usePolling")) { - r = ""+callbackServer.usePolling(); - } - else if (message.equals("restartServer")) { - callbackServer.restartServer(); - } - else if (message.equals("getPort")) { - r = Integer.toString(callbackServer.getPort()); - } - else if (message.equals("getToken")) { - r = callbackServer.getToken(); - } - result.confirm(r); - } - - // PhoneGap JS has initialized, so show webview - // (This solves white flash seen when rendering HTML) - else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) { - appView.setVisibility(View.VISIBLE); - ctx.spinnerStop(); - result.confirm("OK"); - } + + // Security check to make sure any requests are coming from the page initially + // loaded in webview and not another loaded in an iframe. + boolean reqOk = false; + if (url.indexOf(this.ctx.baseUrl) == 0) { + reqOk = true; + } + + // Calling PluginManager.exec() to call a native service using + // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); + if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { + JSONArray array; + try { + array = new JSONArray(defaultValue.substring(4)); + String service = array.getString(0); + String action = array.getString(1); + String callbackId = array.getString(2); + boolean async = array.getBoolean(3); + String r = pluginManager.exec(service, action, callbackId, message, async); + result.confirm(r); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + // Polling for JavaScript messages + else if (reqOk && defaultValue.equals("gap_poll:")) { + String r = callbackServer.getJavascript(); + result.confirm(r); + } + + // Calling into CallbackServer + else if (reqOk && defaultValue.equals("gap_callbackServer:")) { + String r = ""; + if (message.equals("usePolling")) { + r = ""+callbackServer.usePolling(); + } + else if (message.equals("restartServer")) { + callbackServer.restartServer(); + } + else if (message.equals("getPort")) { + r = Integer.toString(callbackServer.getPort()); + } + else if (message.equals("getToken")) { + r = callbackServer.getToken(); + } + result.confirm(r); + } + + // PhoneGap JS has initialized, so show webview + // (This solves white flash seen when rendering HTML) + else if (reqOk && defaultValue.equals("gap_init:")) { + appView.setVisibility(View.VISIBLE); + ctx.spinnerStop(); + result.confirm("OK"); + } - // Show dialog - else { - final JsPromptResult res = result; - AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); - dlg.setMessage(message); - final EditText input = new EditText(this.ctx); - if (defaultValue != null) { - input.setText(defaultValue); - } - dlg.setView(input); - dlg.setCancelable(false); - dlg.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String usertext = input.getText().toString(); - res.confirm(usertext); - } - }); - dlg.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - res.cancel(); - } - }); - dlg.create(); - dlg.show(); - } - return true; + // Show dialog + else { + final JsPromptResult res = result; + AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); + dlg.setMessage(message); + final EditText input = new EditText(this.ctx); + if (defaultValue != null) { + input.setText(defaultValue); + } + dlg.setView(input); + dlg.setCancelable(false); + dlg.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String usertext = input.getText().toString(); + res.confirm(usertext); + } + }); + dlg.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + res.cancel(); + } + }); + dlg.create(); + dlg.show(); + } + return true; } } @@ -982,69 +982,69 @@ public class DroidGap extends PhonegapActivity { */ public class EclairClient extends GapClient { - private String TAG = "PhoneGapLog"; - private long MAX_QUOTA = 100 * 1024 * 1024; + private String TAG = "PhoneGapLog"; + private long MAX_QUOTA = 100 * 1024 * 1024; - /** - * Constructor. - * - * @param ctx - */ - public EclairClient(Context ctx) { - super(ctx); - } + /** + * Constructor. + * + * @param ctx + */ + public EclairClient(Context ctx) { + super(ctx); + } - /** - * Handle database quota exceeded notification. - * - * @param url - * @param databaseIdentifier - * @param currentQuota - * @param estimatedSize - * @param totalUsedQuota - * @param quotaUpdater - */ - @Override - public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, - long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) - { - Log.d(TAG, "event raised onExceededDatabaseQuota estimatedSize: " + Long.toString(estimatedSize) + " currentQuota: " + Long.toString(currentQuota) + " totalUsedQuota: " + Long.toString(totalUsedQuota)); + /** + * Handle database quota exceeded notification. + * + * @param url + * @param databaseIdentifier + * @param currentQuota + * @param estimatedSize + * @param totalUsedQuota + * @param quotaUpdater + */ + @Override + public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, + long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) + { + Log.d(TAG, "event raised onExceededDatabaseQuota estimatedSize: " + Long.toString(estimatedSize) + " currentQuota: " + Long.toString(currentQuota) + " totalUsedQuota: " + Long.toString(totalUsedQuota)); - if( estimatedSize < MAX_QUOTA) - { - //increase for 1Mb - long newQuota = estimatedSize; - Log.d(TAG, "calling quotaUpdater.updateQuota newQuota: " + Long.toString(newQuota) ); - quotaUpdater.updateQuota(newQuota); - } - else - { - // Set the quota to whatever it is and force an error - // TODO: get docs on how to handle this properly - quotaUpdater.updateQuota(currentQuota); - } - } + if( estimatedSize < MAX_QUOTA) + { + //increase for 1Mb + long newQuota = estimatedSize; + Log.d(TAG, "calling quotaUpdater.updateQuota newQuota: " + Long.toString(newQuota) ); + quotaUpdater.updateQuota(newQuota); + } + else + { + // Set the quota to whatever it is and force an error + // TODO: get docs on how to handle this properly + quotaUpdater.updateQuota(currentQuota); + } + } - // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html - @Override - public void onConsoleMessage(String message, int lineNumber, String sourceID) - { - // This is a kludgy hack!!!! - Log.d(TAG, sourceID + ": Line " + Integer.toString(lineNumber) + " : " + message); - } + // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html + @Override + public void onConsoleMessage(String message, int lineNumber, String sourceID) + { + // This is a kludgy hack!!!! + Log.d(TAG, sourceID + ": Line " + Integer.toString(lineNumber) + " : " + message); + } - @Override - /** - * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. - * - * @param origin - * @param callback - */ - public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { - // TODO Auto-generated method stub - super.onGeolocationPermissionsShowPrompt(origin, callback); - callback.invoke(origin, true, false); - } + @Override + /** + * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. + * + * @param origin + * @param callback + */ + public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { + // TODO Auto-generated method stub + super.onGeolocationPermissionsShowPrompt(origin, callback); + callback.invoke(origin, true, false); + } } @@ -1068,221 +1068,221 @@ public class DroidGap extends PhonegapActivity { * Give the host application a chance to take over the control when a new url * is about to be loaded in the current WebView. * - * @param view The WebView that is initiating the callback. - * @param url The url to be loaded. - * @return true to override, false for default behavior + * @param view The WebView that is initiating the callback. + * @param url The url to be loaded. + * @return true to override, false for default behavior */ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - // If dialing phone (tel:5551212) - if (url.startsWith(WebView.SCHEME_TEL)) { - try { - Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error dialing "+url+": "+ e.toString()); - } - return true; - } - - // If displaying map (geo:0,0?q=address) - else if (url.startsWith("geo:")) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error showing map "+url+": "+ e.toString()); - } - return true; - } - - // If sending email (mailto:abc@corp.com) - else if (url.startsWith(WebView.SCHEME_MAILTO)) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error sending email "+url+": "+ e.toString()); - } - return true; - } - - // If sms:5551212?body=This is the message - else if (url.startsWith("sms:")) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); + // If dialing phone (tel:5551212) + if (url.startsWith(WebView.SCHEME_TEL)) { + try { + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error dialing "+url+": "+ e.toString()); + } + return true; + } + + // If displaying map (geo:0,0?q=address) + else if (url.startsWith("geo:")) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error showing map "+url+": "+ e.toString()); + } + return true; + } + + // If sending email (mailto:abc@corp.com) + else if (url.startsWith(WebView.SCHEME_MAILTO)) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error sending email "+url+": "+ e.toString()); + } + return true; + } + + // If sms:5551212?body=This is the message + else if (url.startsWith("sms:")) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); - // Get address - String address = null; - int parmIndex = url.indexOf('?'); - if (parmIndex == -1) { - address = url.substring(4); - } - else { - address = url.substring(4, parmIndex); + // Get address + String address = null; + int parmIndex = url.indexOf('?'); + if (parmIndex == -1) { + address = url.substring(4); + } + else { + address = url.substring(4, parmIndex); - // If body, then set sms body - Uri uri = Uri.parse(url); - String query = uri.getQuery(); - if (query != null) { - if (query.startsWith("body=")) { - intent.putExtra("sms_body", query.substring(5)); - } - } - } - intent.setData(Uri.parse("sms:"+address)); - intent.putExtra("address", address); - intent.setType("vnd.android-dir/mms-sms"); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error sending sms "+url+":"+ e.toString()); - } - return true; - } + // If body, then set sms body + Uri uri = Uri.parse(url); + String query = uri.getQuery(); + if (query != null) { + if (query.startsWith("body=")) { + intent.putExtra("sms_body", query.substring(5)); + } + } + } + intent.setData(Uri.parse("sms:"+address)); + intent.putExtra("address", address); + intent.setType("vnd.android-dir/mms-sms"); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error sending sms "+url+":"+ e.toString()); + } + return true; + } - // All else - else { + // All else + else { - // If our app or file:, then load into our webview - // NOTE: This replaces our app with new URL. When BACK is pressed, - // our app is reloaded and restarted. All state is lost. - if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0) { - try { - // Init parameters to new DroidGap activity and propagate existing parameters - HashMap params = new HashMap(); - String loadingPage = this.ctx.getStringProperty("loadingPageDialog", null); - if (loadingPage != null) { - params.put("loadingDialog", loadingPage); - params.put("loadingPageDialog", loadingPage); - } - if (this.ctx.loadInWebView) { - params.put("loadInWebView", true); - } - params.put("keepRunning", this.ctx.keepRunning); - params.put("loadUrlTimeoutValue", this.ctx.loadUrlTimeoutValue); - String errorUrl = this.ctx.getStringProperty("errorUrl", null); - if (errorUrl != null) { - params.put("errorUrl", errorUrl); - } - params.put("backgroundColor", this.ctx.backgroundColor); + // If our app or file:, then load into our webview + // NOTE: This replaces our app with new URL. When BACK is pressed, + // our app is reloaded and restarted. All state is lost. + if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0) { + try { + // Init parameters to new DroidGap activity and propagate existing parameters + HashMap params = new HashMap(); + String loadingPage = this.ctx.getStringProperty("loadingPageDialog", null); + if (loadingPage != null) { + params.put("loadingDialog", loadingPage); + params.put("loadingPageDialog", loadingPage); + } + if (this.ctx.loadInWebView) { + params.put("loadInWebView", true); + } + params.put("keepRunning", this.ctx.keepRunning); + params.put("loadUrlTimeoutValue", this.ctx.loadUrlTimeoutValue); + String errorUrl = this.ctx.getStringProperty("errorUrl", null); + if (errorUrl != null) { + params.put("errorUrl", errorUrl); + } + params.put("backgroundColor", this.ctx.backgroundColor); - this.ctx.showWebPage(url, true, false, params); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error loading url into DroidGap - "+url+":"+ e.toString()); - } - } - - // If not our application, let default viewer handle - else { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error loading url "+url+":"+ e.toString()); - } - } - return true; - } + this.ctx.showWebPage(url, true, false, params); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error loading url into DroidGap - "+url+":"+ e.toString()); + } + } + + // If not our application, let default viewer handle + else { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error loading url "+url+":"+ e.toString()); + } + } + return true; + } } - + /** * Notify the host application that a page has finished loading. * - * @param view The webview initiating the callback. - * @param url The url of the page. + * @param view The webview initiating the callback. + * @param url The url of the page. */ @Override public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); + super.onPageFinished(view, url); - // Clear timeout flag - this.ctx.loadUrlTimeout++; + // Clear timeout flag + this.ctx.loadUrlTimeout++; - // Try firing the onNativeReady event in JS. If it fails because the JS is - // not loaded yet then just set a flag so that the onNativeReady can be fired - // from the JS side when the JS gets to that code. - if (!url.equals("about:blank")) { - appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); - } + // Try firing the onNativeReady event in JS. If it fails because the JS is + // not loaded yet then just set a flag so that the onNativeReady can be fired + // from the JS side when the JS gets to that code. + if (!url.equals("about:blank")) { + appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); + } - // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly - Thread t = new Thread(new Runnable() { - public void run() { - try { - Thread.sleep(2000); - ctx.runOnUiThread(new Runnable() { - public void run() { - appView.setVisibility(View.VISIBLE); - ctx.spinnerStop(); - } - }); - } catch (InterruptedException e) { - } - } - }); - t.start(); - + // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly + Thread t = new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(2000); + ctx.runOnUiThread(new Runnable() { + public void run() { + appView.setVisibility(View.VISIBLE); + ctx.spinnerStop(); + } + }); + } catch (InterruptedException e) { + } + } + }); + t.start(); + - // Clear history, so that previous screen isn't there when Back button is pressed - if (this.ctx.clearHistory) { - this.ctx.clearHistory = false; - this.ctx.appView.clearHistory(); - } - - // Shutdown if blank loaded - if (url.equals("about:blank")) { - if (this.ctx.callbackServer != null) { - this.ctx.callbackServer.destroy(); - } - } + // Clear history, so that previous screen isn't there when Back button is pressed + if (this.ctx.clearHistory) { + this.ctx.clearHistory = false; + this.ctx.appView.clearHistory(); + } + + // Shutdown if blank loaded + if (url.equals("about:blank")) { + if (this.ctx.callbackServer != null) { + this.ctx.callbackServer.destroy(); + } + } } /** * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable). * The errorCode parameter corresponds to one of the ERROR_* constants. * - * @param view The WebView that is initiating the callback. - * @param errorCode The error code corresponding to an ERROR_* value. - * @param description A String describing the error. - * @param failingUrl The url that failed to load. + * @param view The WebView that is initiating the callback. + * @param errorCode The error code corresponding to an ERROR_* value. + * @param description A String describing the error. + * @param failingUrl The url that failed to load. */ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - System.out.println("onReceivedError: Error code="+errorCode+" Description="+description+" URL="+failingUrl); + System.out.println("onReceivedError: Error code="+errorCode+" Description="+description+" URL="+failingUrl); - // Clear timeout flag - this.ctx.loadUrlTimeout++; + // Clear timeout flag + this.ctx.loadUrlTimeout++; - // Stop "app loading" spinner if showing - this.ctx.spinnerStop(); + // Stop "app loading" spinner if showing + this.ctx.spinnerStop(); - // Handle error - this.ctx.onReceivedError(errorCode, description, failingUrl); + // Handle error + this.ctx.onReceivedError(errorCode, description, failingUrl); } public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - final String packageName = this.ctx.getPackageName(); - final PackageManager pm = this.ctx.getPackageManager(); - ApplicationInfo appInfo; - try { - appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); - if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - // debug = true - handler.proceed(); - return; - } else { - // debug = false - super.onReceivedSslError(view, handler, error); - } - } catch (NameNotFoundException e) { - // When it doubt, lock it out! - super.onReceivedSslError(view, handler, error); - } + final String packageName = this.ctx.getPackageName(); + final PackageManager pm = this.ctx.getPackageManager(); + ApplicationInfo appInfo; + try { + appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + // debug = true + handler.proceed(); + return; + } else { + // debug = false + super.onReceivedSslError(view, handler, error); + } + } catch (NameNotFoundException e) { + // When it doubt, lock it out! + super.onReceivedSslError(view, handler, error); + } } } @@ -1295,47 +1295,47 @@ public class DroidGap extends PhonegapActivity { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (this.appView == null) { - return super.onKeyDown(keyCode, event); + return super.onKeyDown(keyCode, event); } - // If back key - if (keyCode == KeyEvent.KEYCODE_BACK) { + // If back key + if (keyCode == KeyEvent.KEYCODE_BACK) { - // If back key is bound, then send event to JavaScript - if (this.bound) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');"); - return true; - } + // If back key is bound, then send event to JavaScript + if (this.bound) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');"); + return true; + } - // If not bound - else { + // If not bound + else { - // Go to previous page in webview if it is possible to go back - if (this.appView.canGoBack()) { - this.appView.goBack(); - return true; - } + // Go to previous page in webview if it is possible to go back + if (this.appView.canGoBack()) { + this.appView.goBack(); + return true; + } - // If not, then invoke behavior of super class - else { - return super.onKeyDown(keyCode, event); - } - } - } + // If not, then invoke behavior of super class + else { + return super.onKeyDown(keyCode, event); + } + } + } - // If menu key - else if (keyCode == KeyEvent.KEYCODE_MENU) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('menubutton');"); - return true; - } + // If menu key + else if (keyCode == KeyEvent.KEYCODE_MENU) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('menubutton');"); + return true; + } - // If search key - else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('searchbutton');"); - return true; - } + // If search key + else if (keyCode == KeyEvent.KEYCODE_SEARCH) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('searchbutton');"); + return true; + } - return false; + return false; } /** @@ -1344,36 +1344,36 @@ public class DroidGap extends PhonegapActivity { * * This is done to eliminate the need to modify DroidGap.java to receive activity results. * - * @param intent The intent to start - * @param requestCode Identifies who to send the result to + * @param intent The intent to start + * @param requestCode Identifies who to send the result to * * @throws RuntimeException */ @Override public void startActivityForResult(Intent intent, int requestCode) throws RuntimeException { - System.out.println("startActivityForResult(intent,"+requestCode+")"); - super.startActivityForResult(intent, requestCode); + System.out.println("startActivityForResult(intent,"+requestCode+")"); + super.startActivityForResult(intent, requestCode); } /** * Launch an activity for which you would like a result when it finished. When this activity exits, * your onActivityResult() method will be called. * - * @param command The command object - * @param intent The intent to start - * @param requestCode The request code that is passed to callback to identify the activity + * @param command The command object + * @param intent The intent to start + * @param requestCode The request code that is passed to callback to identify the activity */ public void startActivityForResult(IPlugin command, Intent intent, int requestCode) { - this.activityResultCallback = command; - this.activityResultKeepRunning = this.keepRunning; - - // If multitasking turned on, then disable it for activities that return results - if (command != null) { - this.keepRunning = false; - } - - // Start activity - super.startActivityForResult(intent, requestCode); + this.activityResultCallback = command; + this.activityResultKeepRunning = this.keepRunning; + + // If multitasking turned on, then disable it for activities that return results + if (command != null) { + this.keepRunning = false; + } + + // Start activity + super.startActivityForResult(intent, requestCode); } @Override @@ -1381,52 +1381,52 @@ public class DroidGap extends PhonegapActivity { * Called when an activity you launched exits, giving you the requestCode you started it with, * the resultCode it returned, and any additional data from it. * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). */ protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - IPlugin callback = this.activityResultCallback; - if (callback != null) { - callback.onActivityResult(requestCode, resultCode, intent); - } + super.onActivityResult(requestCode, resultCode, intent); + IPlugin callback = this.activityResultCallback; + if (callback != null) { + callback.onActivityResult(requestCode, resultCode, intent); + } } @Override public void setActivityResultCallback(IPlugin plugin) { - this.activityResultCallback = plugin; + this.activityResultCallback = plugin; } /** * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable). * The errorCode parameter corresponds to one of the ERROR_* constants. * - * @param errorCode The error code corresponding to an ERROR_* value. - * @param description A String describing the error. - * @param failingUrl The url that failed to load. + * @param errorCode The error code corresponding to an ERROR_* value. + * @param description A String describing the error. + * @param failingUrl The url that failed to load. */ public void onReceivedError(int errorCode, String description, String failingUrl) { - final DroidGap me = this; + final DroidGap me = this; - // If errorUrl specified, then load it - final String errorUrl = me.getStringProperty("errorUrl", null); - if ((errorUrl != null) && errorUrl.startsWith("file://") && (!failingUrl.equals(errorUrl))) { + // If errorUrl specified, then load it + final String errorUrl = me.getStringProperty("errorUrl", null); + if ((errorUrl != null) && errorUrl.startsWith("file://") && (!failingUrl.equals(errorUrl))) { - // Load URL on UI thread - me.runOnUiThread(new Runnable() { - public void run() { - me.appView.loadUrl(errorUrl); - } - }); - } + // Load URL on UI thread + me.runOnUiThread(new Runnable() { + public void run() { + me.appView.loadUrl(errorUrl); + } + }); + } - // If not, then display error dialog - else { - me.appView.loadUrl("about:blank"); - me.displayError("Application Error", description + " ("+failingUrl+")", "OK", true); - } + // If not, then display error dialog + else { + me.appView.loadUrl("about:blank"); + me.displayError("Application Error", description + " ("+failingUrl+")", "OK", true); + } } /** @@ -1438,26 +1438,26 @@ public class DroidGap extends PhonegapActivity { * @param exit */ public void displayError(final String title, final String message, final String button, final boolean exit) { - final DroidGap me = this; - me.runOnUiThread(new Runnable() { - public void run() { - AlertDialog.Builder dlg = new AlertDialog.Builder(me); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(false); - dlg.setPositiveButton(button, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - if (exit) { - me.finish(); - } - } - }); - dlg.create(); - dlg.show(); - } - }); + final DroidGap me = this; + me.runOnUiThread(new Runnable() { + public void run() { + AlertDialog.Builder dlg = new AlertDialog.Builder(me); + dlg.setMessage(message); + dlg.setTitle(title); + dlg.setCancelable(false); + dlg.setPositiveButton(button, + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + if (exit) { + me.finish(); + } + } + }); + dlg.create(); + dlg.show(); + } + }); } /** @@ -1466,77 +1466,77 @@ public class DroidGap extends PhonegapActivity { */ class LinearLayoutSoftKeyboardDetect extends LinearLayout { - private static final String LOG_TAG = "SoftKeyboardDetect"; - - private int oldHeight = 0; // Need to save the old height as not to send redundant events - private int oldWidth = 0; // Need to save old width for orientation change - private int screenWidth = 0; - private int screenHeight = 0; - - public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { - super(context); - screenWidth = width; - screenHeight = height; - } + private static final String LOG_TAG = "SoftKeyboardDetect"; + + private int oldHeight = 0; // Need to save the old height as not to send redundant events + private int oldWidth = 0; // Need to save old width for orientation change + private int screenWidth = 0; + private int screenHeight = 0; + + public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { + super(context); + screenWidth = width; + screenHeight = height; + } - @Override - /** - * Start listening to new measurement events. Fire events when the height - * gets smaller fire a show keyboard event and when height gets bigger fire - * a hide keyboard event. - * - * Note: We are using callbackServer.sendJavascript() instead of - * this.appView.loadUrl() as changing the URL of the app would cause the - * soft keyboard to go away. - * - * @param widthMeasureSpec - * @param heightMeasureSpec - */ - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - Log.d(LOG_TAG, "We are in our onMeasure method"); + @Override + /** + * Start listening to new measurement events. Fire events when the height + * gets smaller fire a show keyboard event and when height gets bigger fire + * a hide keyboard event. + * + * Note: We are using callbackServer.sendJavascript() instead of + * this.appView.loadUrl() as changing the URL of the app would cause the + * soft keyboard to go away. + * + * @param widthMeasureSpec + * @param heightMeasureSpec + */ + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + Log.d(LOG_TAG, "We are in our onMeasure method"); - // Get the current height of the visible part of the screen. - // This height will not included the status bar. - int height = MeasureSpec.getSize(heightMeasureSpec); - int width = MeasureSpec.getSize(widthMeasureSpec); - - Log.d(LOG_TAG, "Old Height = " + oldHeight); - Log.d(LOG_TAG, "Height = " + height); - Log.d(LOG_TAG, "Old Width = " + oldWidth); - Log.d(LOG_TAG, "Width = " + width); - - - // If the oldHeight = 0 then this is the first measure event as the app starts up. - // If oldHeight == height then we got a measurement change that doesn't affect us. - if (oldHeight == 0 || oldHeight == height) { - Log.d(LOG_TAG, "Ignore this event"); - } - // Account for orientation change and ignore this event/Fire orientation change - else if(screenHeight == width) - { - int tmp_var = screenHeight; - screenHeight = screenWidth; - screenWidth = tmp_var; - Log.d(LOG_TAG, "Orientation Change"); - } - // If the height as gotten bigger then we will assume the soft keyboard has - // gone away. - else if (height > oldHeight) { - Log.d(LOG_TAG, "Throw hide keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');"); - } - // If the height as gotten smaller then we will assume the soft keyboard has - // been displayed. - else if (height < oldHeight) { - Log.d(LOG_TAG, "Throw show keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');"); - } + // Get the current height of the visible part of the screen. + // This height will not included the status bar. + int height = MeasureSpec.getSize(heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + + Log.d(LOG_TAG, "Old Height = " + oldHeight); + Log.d(LOG_TAG, "Height = " + height); + Log.d(LOG_TAG, "Old Width = " + oldWidth); + Log.d(LOG_TAG, "Width = " + width); + + + // If the oldHeight = 0 then this is the first measure event as the app starts up. + // If oldHeight == height then we got a measurement change that doesn't affect us. + if (oldHeight == 0 || oldHeight == height) { + Log.d(LOG_TAG, "Ignore this event"); + } + // Account for orientation change and ignore this event/Fire orientation change + else if(screenHeight == width) + { + int tmp_var = screenHeight; + screenHeight = screenWidth; + screenWidth = tmp_var; + Log.d(LOG_TAG, "Orientation Change"); + } + // If the height as gotten bigger then we will assume the soft keyboard has + // gone away. + else if (height > oldHeight) { + Log.d(LOG_TAG, "Throw hide keyboard event"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');"); + } + // If the height as gotten smaller then we will assume the soft keyboard has + // been displayed. + else if (height < oldHeight) { + Log.d(LOG_TAG, "Throw show keyboard event"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');"); + } - // Update the old height for the next event - oldHeight = height; - oldWidth = width; - } + // Update the old height for the next event + oldHeight = height; + oldWidth = width; + } } -} +} \ No newline at end of file diff --git a/framework/src/com/phonegap/ExifHelper.java b/framework/src/com/phonegap/ExifHelper.java new file mode 100644 index 00000000..cd9e0a0c --- /dev/null +++ b/framework/src/com/phonegap/ExifHelper.java @@ -0,0 +1,153 @@ +/* + * 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) 2011, IBM Corporation + */ +package com.phonegap; + +import java.io.IOException; + +import android.media.ExifInterface; + +public class ExifHelper { + private String aperature = null; + private String datetime = null; + private String exposureTime = null; + private String flash = null; + private String focalLength = null; + private String gpsAltitude = null; + private String gpsAltitudeRef = null; + private String gpsDateStamp = null; + private String gpsLatitude = null; + private String gpsLatitudeRef = null; + private String gpsLongitude = null; + private String gpsLongitudeRef = null; + private String gpsProcessingMethod = null; + private String gpsTimestamp = null; + private String iso = null; + private String make = null; + private String model = null; + private String orientation = null; + private String whiteBalance = null; + + private ExifInterface inFile = null; + private ExifInterface outFile = null; + + /** + * The file before it is compressed + * + * @param filePath + * @throws IOException + */ + public void createInFile(String filePath) throws IOException { + this.inFile = new ExifInterface(filePath); + } + + /** + * The file after it has been compressed + * + * @param filePath + * @throws IOException + */ + public void createOutFile(String filePath) throws IOException { + this.outFile = new ExifInterface(filePath); + } + + /** + * Reads all the EXIF data from the input file. + */ + public void readExifData() { + this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE); + this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME); + this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME); + this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH); + this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH); + this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE); + this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF); + this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP); + this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE); + this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF); + this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE); + this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF); + this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD); + this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP); + this.iso = inFile.getAttribute(ExifInterface.TAG_ISO); + this.make = inFile.getAttribute(ExifInterface.TAG_MAKE); + this.model = inFile.getAttribute(ExifInterface.TAG_MODEL); + this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION); + this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE); + } + + /** + * Writes the previously stored EXIF data to the output file. + * + * @throws IOException + */ + public void writeExifData() throws IOException { + // Don't try to write to a null file + if (this.outFile == null) { + return; + } + + if (this.aperature != null) { + this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature); + } + if (this.datetime != null) { + this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime); + } + if (this.exposureTime != null) { + this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime); + } + if (this.flash != null) { + this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash); + } + if (this.focalLength != null) { + this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength); + } + if (this.gpsAltitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude); + } + if (this.gpsAltitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef); + } + if (this.gpsDateStamp != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp); + } + if (this.gpsLatitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude); + } + if (this.gpsLatitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef); + } + if (this.gpsLongitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude); + } + if (this.gpsLongitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef); + } + if (this.gpsProcessingMethod != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod); + } + if (this.gpsTimestamp != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp); + } + if (this.iso != null) { + this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso); + } + if (this.make != null) { + this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make); + } + if (this.model != null) { + this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model); + } + if (this.orientation != null) { + this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation); + } + if (this.whiteBalance != null) { + this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance); + } + + this.outFile.saveAttributes(); + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java index 240a507e..6d6ad655 100755 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -25,6 +25,7 @@ import android.provider.MediaStore; import android.util.Log; import android.webkit.MimeTypeMap; +import com.phonegap.api.PhonegapActivity; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; import com.phonegap.file.EncodingException; @@ -39,7 +40,8 @@ import com.phonegap.file.TypeMismatchException; */ public class FileUtils extends Plugin { private static final String LOG_TAG = "FileUtils"; - + private static final String _DATA = "_data"; // The column name where the file path is stored + public static int NOT_FOUND_ERR = 1; public static int SECURITY_ERR = 2; public static int ABORT_ERR = 3; @@ -988,5 +990,19 @@ public class FileUtils extends Plugin { return new FileInputStream(path); } } + + /** + * Queries the media store to find out what the file path is for the Uri we supply + * + * @param contentUri the Uri of the audio/image/video + * @param ctx the current applicaiton context + * @return the full path to the file + */ + protected static String getRealPathFromURI(Uri contentUri, PhonegapActivity ctx) { + String[] proj = { _DATA }; + Cursor cursor = ctx.managedQuery(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(_DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } } -