diff --git a/framework/src/com/phonegap/ActivityResultModule.java b/framework/src/com/phonegap/ActivityResultModule.java new file mode 100755 index 00000000..c1649a24 --- /dev/null +++ b/framework/src/com/phonegap/ActivityResultModule.java @@ -0,0 +1,49 @@ +package com.phonegap; + +import android.content.Intent; +import android.webkit.WebView; + +/** + * This class should be extended by a PhoneGap module to register an onActivityResult() callback + * with DroidGap. It allows modules to start activities with result callback without having to + * modify DroidGap.java. + */ +public abstract class ActivityResultModule extends Module { + + public int requestCode; + + /** + * Constructor. + * + * @param view + * @param gap + */ + public ActivityResultModule(WebView view, DroidGap gap) { + super(view, gap); + this.requestCode = gap.addActivityResult(this); + } + + /** + * 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"). + */ + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + } + + /** + * Launch an activity for which you would like a result when it finished. When this activity exits, + * your onActivityResult() method will be called. + * + * @param intent + */ + public void startActivityForResult(Intent intent) { + this.gap.startActivityForResult(intent, this.requestCode); + } + + +} diff --git a/framework/src/com/phonegap/CameraLauncher.java b/framework/src/com/phonegap/CameraLauncher.java old mode 100644 new mode 100755 index 3ff9ec73..c026538b --- a/framework/src/com/phonegap/CameraLauncher.java +++ b/framework/src/com/phonegap/CameraLauncher.java @@ -2,17 +2,18 @@ package com.phonegap; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.OutputStream; import org.apache.commons.codec.binary.Base64; import android.app.Activity; -import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.net.Uri; import android.os.Environment; -import android.provider.MediaStore; import android.webkit.WebView; /** @@ -20,12 +21,11 @@ import android.webkit.WebView; * and returns the captured image. When the camera view is closed, the screen displayed before * the camera view was shown is redisplayed. */ -public class CameraLauncher { +public class CameraLauncher extends ActivityResultModule { - private WebView mAppView; // Webview object - private DroidGap mGap; // DroidGap object private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) private Uri imageUri; // Uri of captured image + private boolean base64 = false; /** * Constructor. @@ -33,9 +33,8 @@ public class CameraLauncher { * @param view * @param gap */ - CameraLauncher(WebView view, DroidGap gap) { - mAppView = view; - mGap = gap; + public CameraLauncher(WebView view, DroidGap gap) { + super(view, gap); } /** @@ -50,10 +49,14 @@ public class CameraLauncher { // Display camera Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); + + // Specify file so that large image is captured and returned + // TODO: What if there isn't any external storage? File photo = new File(Environment.getExternalStorageDirectory(), "Pic.jpg"); - intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); + intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); this.imageUri = Uri.fromFile(photo); - mGap.startActivityForResult(intent, DroidGap.CAMERA_ACTIVIY_RESULT); + + this.startActivityForResult(intent); } /** @@ -64,20 +67,53 @@ public class CameraLauncher { * @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"). */ + @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); // If image available if (resultCode == Activity.RESULT_OK) { - Uri selectedImage = this.imageUri; - mGap.getContentResolver().notifyChange(selectedImage, null); - ContentResolver cr = mGap.getContentResolver(); - Bitmap bitmap; - try { - bitmap = android.provider.MediaStore.Images.Media.getBitmap(cr, selectedImage); - this.processPicture(bitmap); - } catch (Exception e) { + try { + // Read in bitmap of captured image + Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.gap.getContentResolver(), imageUri); + + // If sending base64 image back + if (this.base64) { + this.processPicture(bitmap); + } + + // If sending filename back + else { + // 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.gap.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.gap.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; + } + } + + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.gap.getContentResolver().openOutputStream(uri); + bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); + os.close(); + + // Send Uri back to JavaScript for viewing image + this.sendJavascript("navigator.camera.success('" + uri.toString() + "');"); + } + } catch (IOException e) { + e.printStackTrace(); this.failPicture("Error capturing image."); - } + } } // If cancelled @@ -103,7 +139,7 @@ public class CameraLauncher { byte[] code = jpeg_data.toByteArray(); byte[] output = Base64.encodeBase64(code); String js_out = new String(output); - mGap.sendJavascript("navigator.camera.success('" + js_out + "');"); + this.sendJavascript("navigator.camera.success('" + js_out + "');"); } } catch(Exception e) { @@ -117,6 +153,6 @@ public class CameraLauncher { * @param err */ public void failPicture(String err) { - mGap.sendJavascript("navigator.camera.error('" + err + "');"); + this.sendJavascript("navigator.camera.error('" + err + "');"); } } diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 07a2f194..40eb4543 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -80,7 +80,6 @@ import android.os.Build.*; public class DroidGap extends Activity { private static final String LOG_TAG = "DroidGap"; - public static final int CAMERA_ACTIVIY_RESULT = 1; protected WebView appView; // The webview for our app protected ImageView splashScreen; @@ -99,12 +98,16 @@ public class DroidGap extends Activity { private CryptoHandler crypto; private BrowserKey mKey; private AudioHandler audio; - private CallbackServer callbackServer; + public CallbackServer callbackServer; private CommandManager commandManager; private String url; // The initial URL for our app private String baseUrl; // The base of the initial URL for our app - + + // Variables to manage ActivityResultCallbacks + private int activityResultCallbackCounter = 1000; + private HashMap activityResultCallbacks = new HashMap(); + /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { @@ -609,6 +612,39 @@ public class DroidGap extends Activity { root.addView(appView); } + /** + * Any calls to Activity.startActivityForResult must go through ActivityResultCallback, so + * the result can be routed to them correctly. + * + * 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 + * + * @throws RuntimeException + */ + @Override + public void startActivityForResult(Intent intent, int requestCode) throws RuntimeException { + if (this.activityResultCallbacks.containsKey(requestCode)) { + super.startActivityForResult(intent, requestCode); + } + else { + throw new RuntimeException("PhoneGap Exception: Do not call startActivityForResult() directly. Implement ActivityResultCallback instead."); + } + } + + /** + * Add activity result callback to receive onActivityResult() callbacks. + * + * @param callback The callback class + * @return The request code to use for the callback + */ + public int addActivityResult(ActivityResultModule callback) { + int requestCode = this.activityResultCallbackCounter++; + this.activityResultCallbacks.put(requestCode, callback); + return requestCode; + } + @Override /** * Called when an activity you launched exits, giving you the requestCode you started it with, @@ -622,9 +658,9 @@ public class DroidGap extends Activity { protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); - // If camera result - if (requestCode == DroidGap.CAMERA_ACTIVIY_RESULT) { - launcher.onActivityResult(requestCode, resultCode, intent); - } + ActivityResultModule callback = this.activityResultCallbacks.get(requestCode); + if (callback != null) { + callback.onActivityResult(requestCode, resultCode, intent); + } } } diff --git a/framework/src/com/phonegap/Module.java b/framework/src/com/phonegap/Module.java new file mode 100755 index 00000000..196e94d0 --- /dev/null +++ b/framework/src/com/phonegap/Module.java @@ -0,0 +1,55 @@ +package com.phonegap; + +import android.webkit.WebView; + +/** + * This class represents a PhoneGap module and should be extended by all modules + * that provide functionality to PhoneGap. If the module invokes an activity and + * expects a result back (see CameraLauncher), then it should extend ActivityResultModule + * instead. + */ +public abstract class Module { + + protected DroidGap gap; // DroidGap object + protected WebView view; // WebView object + + /** + * Constructor. + * + * @param view + * @param gap + */ + public Module(WebView view, DroidGap gap) { + this.gap = gap; + this.view = view; + } + + /** + * Called when the system is about to start resuming a previous activity. + */ + public void onPause() { + } + + /** + * Called when the activity will start interacting with the user. + */ + public void onResume() { + } + + /** + * The final call you receive before your activity is destroyed. + */ + public void onDestroy() { + } + + /** + * Send JavaScript statement back to JavaScript. + * + * @param message + */ + public void sendJavascript(String statement) { + System.out.println("Module.sendResponse("+statement+")"); + this.gap.callbackServer.sendJavascript(statement); + } + +}