Add automatic handling of onActivityResult, so modules that start activities with result callback can do so without modifying DroidGap.java.

This commit is contained in:
Bryce Curtis 2010-09-03 14:01:24 -05:00
parent 8163416e27
commit f77e51290b
4 changed files with 204 additions and 28 deletions

View File

@ -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);
}
}

78
framework/src/com/phonegap/CameraLauncher.java Normal file → Executable file
View File

@ -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 + "');");
}
}

View File

@ -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<Integer,ActivityResultModule> activityResultCallbacks = new HashMap<Integer,ActivityResultModule>();
/** 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);
}
}
}

View File

@ -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);
}
}